Ограничения языка

Ü имеет ряд ограничений, не позволяющих реализовать некоторые конструкции и типовые шаблоны. Ниже перечислены некоторые из этих ограничений.

Ограничение на вызов методов непосредственных значений

Непосредственные значения - переменные, не имеющие имени и существующие здесь и сейчас. Пример таковых - результаты-значения вызова функции, результат конструирования временной переменной, результат операторов move и take.

Компилятор считает, что такие значения нельзя изменять, т. к. изменение не имеет особого смысла - оно не может быть наблюдаемым позже, ибо объект не может быть доступен, если у него нету имени. Невозможность изменений означает, что такие значения нельзя передавать в функции через mut ссылку.

При этом также невозможно вызвать и методы структур или классов, если они принимают this через mut ссылку. Обычно это не сильно надо, но в редких случаях всё же может иметь смысл.

Известный пример - вспомогательные классы lock_mut для shared_ptr типов. Этот класс является неким прокси для изменения хранимого значения. При этом такое изменение требует, чтобы метод deref типа lock_mut возвращал mut ссылку.

Из-за этого нельзя писать что-то вроде ptr.lock_mut().deref()= 42;, т. к. метод deref принимает this как mut ссылку. Приходится писать вместо этого auto mut lock= ptr.lock_mut(); lock.deref()= 42;. Особенно это неудобно в сравнении с lock_imut объектом, где краткая запись возможна: auto val= ptr.lock_imut().deref();.

Ограничения ссылочных полей

Ссылочное поле можно создать для любого типа без ссылок внутри. Но вот если тип содержит ссылки внутри, то действуют ряд ограничений. Такое поле должно иметь не более одного внутреннего ссылочного тега внутри. Также недопустимо, чтобы в этом типе были ссылочные поля типов со ссылками внутри. Таким образом, глубина ссылочной косвенности не может быть больше 2.

Ссылки косвенности 2 (второго порядка) имеют ограничение в использовании. Их нельзя возвращать из функций, функции не могут их связывать. Продиктовано это ограничение тем фактом, что в языке не существует ссылочной нотации для обозначения ссылок второго порядка.

Ссылки второго порядка учитываются компилятором весьма хитрым образом и в редких случаях при работе с ними могут возникать ложно-положительные ошибки контроля ссылок. Ссылки более чем второго порядка не поддерживаются, т. к. их поддержка добавила бы в механизм контроля ссылок ещё больше сложности, чем там и так уже есть. Да и не вполне ясно, нужно ли вообще кому-то создавать ссылки более чем второго порядка, в подавляющем большинстве случаев хватает и ссылок первого порядка.

Невозможность создать две изменяемые ссылки на один и тот же объект

Контроль ссылок не позволяет создавать более одной mut ссылки на один и тот же объект. Единственное исключение - доступ к разным полям структур и классов. Данное ограничение в целом правильно и логично. Но существуют случаи, когда ссылки можно было бы создавать, но этого всё равно сделать не получится.

Например, тот же библиотечный класс vector мог бы иметь метод, возвращающий пару ссылок на различные элементы или на непересекающиеся диапазоны. Логически в этом нет ничего плохого - главное правило контроля ссылок не нарушается. Но фактически такое сделать не получится, т. к. обе результирующие ссылки будут считаться mut ссылками на исходный vector, что приведёт к порождению ошибки компиляции.

Невозможность внутренней изменяемости без аллокации в куче

В Ü через imut ссылки нельзя изменить какую-либо часть непосредственной памяти переменной. Можно конечно попытаться такое сделать при помощи unsafe, но это приведёт к неопределённому поведению. Например, код может сломаться из-за некоторой оптимизации в недрах компилятора, рассчитанной на то, что память доступная по imut ссылке не меняется.

Единственная возможность реализовать внутреннюю изменяемость - изменять косвенно-доступную память, например, если она выделена из кучи. Именно таким образом реализуются библиотечные классы вроде shared_ptr - разделяемый объект и его счётчик ссылок выделяются в куче.

Данная особенность делает невозможным, например, использование атомарных переменных без использования кучи. По-идее, изменяемость атомарных переменных потокобезопасна и поэтому их можно было бы разделять между разными потоками. Но через mut ссылки это делать нельзя, т. к. компилятор считает это множественным одновременным изменением и генерирует ошибку. Через imut ссылки это тоже не получится, т. к. компилятор считает, что память через них не меняется. Единственный вариант - иметь класс-обёртку для атомарных переменных, который бы выделял для них память в куче.

Невозможность глобальных изменяемых переменных не-constexpr типов

Даже если глобальная переменная объявлена изменяемой, её инициализатор всё равно должен быть constexpr. А это возможно только для constexpr типов. А таковыми типами не являются, например, сырые указатели и всё, что их содержит. Любой класс тоже не является constexpr.

Это делает невозможным простую реализацию сложных глобальных изменяемых объектов. Но это всё же возможно, хоть и через костыли. Например, можно выделить объект в куче и преобразовать указатель на него в size_type, который уже сохранить в глобальной изменяемой переменной.

Невозможность constexpr инициализации конструктором

Пока что в Ü нельзя непосредственно проинициализировать constexpr переменную типа структуры непосредственно через её конструктор.

struct S{}
var S constexpr s0; // Ошибка - вызов конструктора по умолчанию, который не порождает constexpr инициализатора
var S constexpr s1{}; // А так можно

Проблема лежит в реализации компилятора - он не умеет вызывать как constexpr функции с mut ссылочными параметрами, каковыми являются конструкторы. Но проблему можно обойти через функцию-обёртку ( в т. ч. лямбду).

struct S{}
var S constexpr s0= lambda[]() : S { return S(); } ();

Отсутствие информации о шаблонных методах в typeinfo

typeinfo для структур и классов не содержит никакой информации о шаблонных методах. Они вообще не перечислены в списке методов.

Проблемой является потенциальное представление таких методов в typeinfo. Для них нельзя как обычно предоставить информацию об их типе, т. к. он может быть различным для различных шаблонных аргументов. Данная проблема пока не решена и вместо этого шаблонные методы просто игнорируются.

Отсутствие информации о шаблонных методах делает невозможным их учёт в полиморфном коде, проверяющем наличие каких-либо методов в переданных типах. Такой код может не работать для классов, где нужные методы шаблонные.

Отсутствие в языке нотации для разделяемых библиотек

Пока что в Ü нету способа пометить функцию, как экспортируемую/импортируемую, вроде __declspec(dllexport)/__declspec(dllimport) из C++. Поэтому для передачи функций через границы разделяемых библиотек нужно или писать map файлы, или делать C обёртки.

Для исполняемых файлов формата ELF ситуация лучше - там любая внешне-доступная функция может быть экспортирована/импортирована. Но всплывает обратная проблема - экспортируется и то, что не надо, тем самым раздувая таблицу экспорта.

Ограничения non_sync типов

Тег non_sync сейчас требуется проставлять для всех классов, которые реализуют потоконебезопасную внутреннюю изменяемость для неизменяемых объектов. Типы, помеченные как non_sync, нельзя передавать в другой поток - будь то значения или неизменяемые ссылки, дабы предотвратить конкурентное изменение из нескольких потоков.

Типичный пример non_sync типа - shared_ptr классы. Их счётчики ссылок и счётчики доступа к значению не могут быть корректно изменены из разных потоков. Поэтому существуют многопоточные версии shared_ptr классов, где счётчики изменяются атомарно и посему эти типы уже не являются non_sync.

Но ограничение концепции non_sync в Ü может сказываться на типах, которые хоть и содержат потоконебезопасную внутреннюю изменяемость, но всё же (гипотетически) могли бы быть переданы в другой поток. Пример такого типа - контейнер Cell стандартной библиотеки языка программирования Rust. Этот контейнер можно безопасно передавать по значению в другой поток, но не по ссылке. Достигается это за счёт существования отдельных типажей Send и Sync, когда для Cell реализован только Send, но не Sync. В Ü же non_sync по сути объединяет в себе свойства, аналогичные отсутствию Send или Sync, что делает невозможными контейнеры вроде Cell из Rust.