Тег non_sync

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

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

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

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

class A non_sync {} // Безусловно non_sync
struct B non_sync( true ) {} // non_sync по условию
class C non_sync( false ) {} // не-non_sync по условию
class D{ A a; } // non_sync, т. к. содержит non_sync элементы
class E polymorph non_sync {}
class F : E { } // non_sync, т. к. имеет non_sync предка

// Фундаментальные типы не являются non_sync
static_assert( !non_sync</i32/> );
static_assert( !non_sync</bool/> );
static_assert( !non_sync</[f32, 4]/> );

static_assert( non_sync</A/> );
static_assert( non_sync</[A, 8]/> ); // Массив non_sync элементов является non_sync
static_assert( non_sync</B/> );
static_assert( non_sync</tup[bool, B, char8]/> ); // Кортеж с хотя бы одним non_sync элементом является non_sync
static_assert( !non_sync</C/> );
static_assert( !non_sync</[C, 7]/> );
static_assert( non_sync</D/> );
static_assert( non_sync</E/> );
static_assert( non_sync</F/> );

При объявлении non_sync тега условным возможно при этом создать цикл зависимостей. Это не страшно, пока выражение в условном non_sync теге является non_sync выражением.

class A non_sync( non_sync</A/> ) {} // самозависимость - результат будет не non_sync
static_assert( !non_sync</A/> );

class B non_sync( non_sync</C/> ) {} // циклическая зависимость - результат не будет non_sync, т. к. нету источника начального non_sync тега
class C non_sync( non_sync</B/> ) {}
static_assert( !non_sync</B/> );
static_assert( !non_sync</C/> );

class D non_sync( non_sync</E/> ) {} // циклическая зависимость через поле - результат будет non_sync, т. к. есть источник начального non_sync тега
class E non_sync { D d; }
static_assert( non_sync</D/> );
static_assert( non_sync</E/> );

Свйоство non_sync нельзя изменять при наследовании. Если non_sync тип имеет не-non_sync предка, компилятор выдаст ошибку компиляции. Это необходимо, чтобы не терялось наличие non_sync свойства при сохранении экземпляра производного класса через ссылку или контейнер для базового класса.

Использование тега non_sync

В обычном пользовательском коде тег non_sync использовать нету нужды, т. к. без unsafe сделать что-то non_sync не выйдет.

Использовать тег non_sync нужно только непосредственно в контейнерах, которые при помощи unsafe кода реализуют некоторую потоконебезопасную внутреннюю изменяемость. Кроме того, в контейнерах с непрямым хранением значений (box, vector, variant и т. д.) надо использовать условный тег non_sync, зависящий от хранимого типа, дабы свойство non_sync рекурсивно распространялось через эти контейнеры.

В коде, который каким-либо образом создаёт потоки, или в коде, где какой-либо объект передаётся в другой поток, следует добавлять static_assert( !non_sync</T/> ), чтобы предотвратить использование небезопасных для многопоточного доступа типов.

Безопасная многопоточная изменяемость

Как было отмечено выше, значения типов, являющихся non_sync нельзя использовать в многопоточной среде. Но что, если всё же нужна внутренняя изменяемость совместно с многопоточностью? Решение - использовать типы, реализующие потокобезопасную внутреннюю изменяемость. Эти типы не помечены как non_sync. Внутри они используют какие-либо примитивы синхронизации, чтобы гарантировать отсутствие гонок. Примеры таких примитивов - mutex, rw_lock, атомарные переменные и т. д.

При этом типы контейнеров с синхронизацией внутренней изменяемости должны проверять хранимый тип на отсутствие внутренней изменяемости - через static_assert( !non_sync</T/> ).