В Rust безопасность при работе с потоками обеспечивается за счёт двух автоматических трейтов: Send и Sync.
Send позволяет передавать тип между потоками (путём передачи владения).Sync гарантирует, что тип можно безопасно использовать из нескольких потоков одновременно (за счёт ссылок).Большинство стандартных типов в Rust по умолчанию реализуют эти трейты. Например, Arc<T>, Mutex<T> — Send и Sync (если T тоже соответствует этим трейтам).
Вы можете явно запретить или реализовать эти трейты для своих типов. Например, если у вас есть внутреннее небезопасное поле (например, голый указатель или иносторонние ресурсы), следует сделать типы !Send или !Sync:
use std::marker::PhantomData; use std::rc::Rc; struct MyType { not_thread_safe: Rc<u32>, _marker: PhantomData<*const ()>, } // Rc<u32> не реализует Send/Sync, поэтому MyType также не будет реализовать эти трейты.
Если вы реализуете низкоуровневую обёртку и ручное управление потокобезопасностью, можно реализовать Send/Sync вручную (unsafe):
unsafe impl Send for MyType {} unsafe impl Sync for MyType {}
Это ответственность программиста — дать гарантии потокобезопасности.
Что произойдёт, если вынести Rc<T> в несколько потоков через Arc<Rc<T>>?
Часто думают, что Arc всё защищает. Но Rc<T> не реализует Send/Sync, даже если обернуть в Arc! Вот так:
use std::rc::Rc; use std::sync::Arc; fn main() { let data = Arc::new(Rc::new(5)); // std::thread::spawn(move || { // println!("{:?}", data); // }); // Компилятор не даст это сделать! }
Arc не компенсирует отсутствие Send/Sync у вложенных членов.
История
В проекте была попытка использовать Arc<Rc<T>>, чтобы разделить данные между потоками и неблокирующим образом делить владение. Программа падала во время исполнения с непредсказуемым поведением; оказалось, что Rc не потокобезопасен, и этого изначально не хватило знаний о трейтах Send/Sync.
История
В self-made event loop тип State хранил сырой указатель на данные. Тип State пометили unsafe impl Send, но забыли расставить синхронизацию. В итоге возникла классическая data race, выявленная уже после релиза.
История
Разработчик реализовал newtype обёртку над Mutex, но ошибочно сделал её !Sync, не реализовав Sync вручную. Это мешало использовать тип в многопоточном контексте (например, внутри Arc<Mutex<T>>), т.к. компилятор требовал Sync. Исправлено реализацией unsafe impl Sync и анализом безопасности.