Требования к небезопасному коду

Существует ряд правил, которые необходимо соблюдать в небезопасном коде. Их несоблюдение может привести к непредвиденным последствиям.

Неинициализированные данные

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

Контроль ссылок

Нельзя с помощью небезопасного кода нарушать правила контроля ссылок.

Нельзя создавать ссылки, которые переживают данные, на которые они ссылаются. В противном случае возможен доступ к уже разрушенным данным, что чревато неопределённым поведением.

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

Ненулевые ссылки

Ссылки обязаны быть ненулевыми. В это требования ходят ссылки-аргументы функций, ссылки-возвращаемые значения, локальные ссылки, ссылки в структурах и какие-либо другие ссылки.

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

Вместо нулевых ссылок следует (если необходимо) использовать сырые указатели. Они могут быть нулевыми.

Небезопасное преобразование типов ссылок

С помощью встроенного оператора cast_ref_unsafe можно преобразовать ссылку одного типа в любой другой. При этом с полученной ссылкой следует работать с осторожностью.

Предполагается, что через ссылку определённого типа можно прочитать значение этого типа или его части (поле структуры, элемент массива и т. д.). Фронтенд компилятора помечает ссылки атрибутом, обозначающим, что по этой ссылке можно прочитать как минимум количество байт, равное размеру типа ссылки. Бекенд компилятора поэтому может вставлять спекулятивные операции чтения памяти в рамках этого размера, не опасаясь чтения из недоступной области памяти. Если же реально через исходную ссылку, из которой была получена текущая ссылка, доступно меньшее количество байт, может произойти чтение памяти в том числе из недопустимой области.

Кроме того фронтенд компилятора специальным образом помечает инструкции чтения/записи памяти типом, через которые эти операции помечаются. Оптимизатор компилятора считает, что разные типы не могут существовать в одной области памяти, и может опираясь на это оптимизировать обращения к памяти.

Из вышеописанного правила есть ряд исключений. Считается, что значения типа byte8 могут занимать ту же область памяти, что и любые другие значения. Считается, что значения фундаментальных типов, ссылок, указателей, перечислений могут занимать ту же область памяти, что значения byte-типа с тем же размером. Считается, что значения типа byte16 могут занимать ту же область памяти, что значения типа byte8, значения типа byte32 могут занимать ту же область памяти, что значения типа byte16 и т. д.

Из вышеописанного следует, что можно безопасно преобразовывать ссылки любого типа в ссылки на byte8 или в ссылки byte-типа с выравниванием, не большим выравнивания исходного типа. Также возможны обратные преобразования (ссылок byte-типов в ссылки на любые другие типы).

Но, например, преобразование ссылки на f64 сначала в ссылку на byte8, а потом в ссылку на u64 было бы уже неверным.

Безопасным в итоге можно назвать только чтение/запись данных через byte8 ссылку, полученную из любой другой ссылки или byteN ссылку, полученную из данных с выравниванием не меньше N. Также безопасно читать/писать данные, полученные исходно как byte8 (например, через функцию аллокации памяти) через любые другие ссылки.

Неизменяемость

В Ü нельзя изменять значения, помеченные как неизменяемые - глобальные переменные, локальные переменные, аргументы функций а также значения, доступные через неизменяемые ссылки. Изменять нельзя скалярные значения, поля структур/классов, элементы массивов/кортрежей.

Но можно изменять данные, косвенно доступные через неизменяемые данные. Например, можно изменять данные, доступные по изменяемой ссылке в неизменяемой структуре. Можно изменять данные, доступные внутренне через контейнеры стандартной библиотеки (и не только), которые хранятся не непосредственно, а через ссылку/указатель - как например shared_ptr-контейнеры.

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

Кроме того корректная неизменяемость нужна для работы многопоточного кода, чтобы данные, неизменяемая ссылка на которые была передана более чем в один поток, не были действительно изменены, ибо такое изменение было бы несинхронизовано.

Изменение косвенно-доступных переменных в деструкторах

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

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

Несинхронизированная изменяемость

В Ü дозволена косвенная логическая изменяемость через неизменяемые ссылки. Пример классов, использующего эту возможность - shared_ptr-типы стандартной библиотеки, которые дают изменять косвенно хранящиеся (через внутренний указатель) данные через неизменяемую ссылку на объект shared_ptr-типа.

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

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

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

Код, который каким-либо образом создаёт потоки/передаёт данные в другие поток, должен проверять типы вохдных данных, дабы среди не было non_sync-типов.

Внутреннее представление

В Ü запрещено при помощи небезопасного/внешнего кода создавать невозможные правилами языка значения типов.

Не дозволены значения типа bool кроме true и false.

Не дозволены значения типов перечислений, кроме явно объявленных значений. Например, в перечислении enum E{ A, B, C } возможны только бинарные значения 0 (A), 1 (B), 2(C).

Не дозволено менять указатели на таблицы виртуальных функций полиморфных классов.