Ограничения языка¶
Ü имеет ряд ограничений, не позволяющих реализовать некоторые конструкции и типовые шаблоны. Ниже перечислены некоторые из этих ограничений.
Ограничение на вызов методов непосредственных значений¶
Непосредственные значения - переменные, не имеющие имени и существующие здесь и сейчас.
Пример таковых - результаты-значения вызова функции, результат конструирования временной переменной, результат операторов 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.