% Send и Sync

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

  • Тип является Send если его безопасно передавать другому потоку.
  • Тип является Sync если его безопасно разделять между потоками (&T является Send).

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

Send и Sync это к тому же автоматически наследуемые типажи. Это означает, что в отличие от любого другого типажа, если тип состоит только из типов Send или Sync, то он тоже Send или Sync. Почти все примитивные типы являются Send и Sync, и, как следствие, большинство типов, с которыми вы столкнетесь, тоже будут Send и Sync.

Главные исключения:

  • сырые указатели не являются ни Send ни Sync (из-за того, что у них нет охранников безопасности).
  • UnsafeCell не является Sync (и, следовательно, Cell и RefCell тоже).
  • Rc не является Send или Sync (потому что количество ссылок является общим и несинхронизированным ).

Rc и UnsafeCell фундаментально непотокобезопасны: они разрешают несинхронизированное общее изменяемое состояние. Однако сырые указатели, строго говоря, помечены непотокобезопасными в большинстве своем для статического анализа. Для выполнения чего-нибудь полезного с сырыми указателями их необходимо разыменовать, что уже небезопасно. В этом смысле, можно было бы сказать, что их можно пометить потокобезопасными.

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

Типы, для которых Send и Sync не выведены автоматически, могут легко реализовать их:


#![allow(unused)]
fn main() {
struct MyBox(*mut u8);

unsafe impl Send for MyBox {}
unsafe impl Sync for MyBox {}
}

В чрезвычайно редких случаях у типов, которым не нужно автоматическое выведение Send или Sync, необходимо убрать реализацию Send и Sync:


#![allow(unused)]
#![feature(optin_builtin_traits)]

fn main() {
// Я обладаю особой магической семантикой для некоторых примитивов синхронизации!
struct SpecialThreadToken(u8);

impl !Send for SpecialThreadToken {}
impl !Sync for SpecialThreadToken {}
}

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

Большинство типов, использующих сырые указатели, должны быть инкапсулированы в абстракцию, к которой можно вывести Send и Sync. Например, все стандартные коллекции Rust являются Send и Sync (когда содержат типы Send и Sync), несмотря на постоянное использование ими сырых указателей для управления размещением в памяти и сложным владением. Аналогично, большинство итераторов по этим коллекциям являются Send и Sync, потому что они во многом ведут себя как & или &mut в коллекции.

TODO: лучше объяснить, что могут и что не могут Send или Sync. Достаточно ли обращений к гонкам данных?