In Rust, thread safety is ensured through two automatic traits: Send and Sync.
Send allows a type to be transferred between threads (by transferring ownership).Sync guarantees that a type can be safely used from multiple threads simultaneously (via references).Most standard types in Rust implement these traits by default. For example, Arc<T>, Mutex<T> are Send and Sync (if T also adheres to these traits).
You can explicitly forbid or implement these traits for your types. For instance, if you have an internal unsafe field (like a raw pointer or foreign resources), you should make the types !Send or !Sync:
use std::marker::PhantomData; use std::rc::Rc; struct MyType { not_thread_safe: Rc<u32>, _marker: PhantomData<*const ()>, } // Rc<u32> does not implement Send/Sync, so MyType will not implement these traits either.
If you implement a low-level wrapper and manually manage thread safety, you can implement Send/Sync manually (unsafe):
unsafe impl Send for MyType {} unsafe impl Sync for MyType {}
It is the programmer's responsibility to provide guarantees for thread safety.
What happens if you take Rc<T> into several threads via Arc<Rc<T>>?
It is often thought that Arc protects everything. But Rc<T> does not implement Send/Sync, even when wrapped in Arc! Like this:
use std::rc::Rc; use std::sync::Arc; fn main() { let data = Arc::new(Rc::new(5)); // std::thread::spawn(move || { // println!("{:?}", data); // }); // The compiler will not allow this! }
Arc does not compensate for the lack of Send/Sync for nested members.
Story
In the project, there was an attempt to use Arc<Rc<T>> to share data between threads and non-blockingly share ownership. The program crashed during execution with unpredictable behavior; it turned out that Rc is not thread-safe, and this initial lack of knowledge about the Send/Sync traits was critical.
Story
In a self-made event loop, the State type held a raw pointer to the data. The State type was marked as unsafe impl Send, but synchronization was forgotten. As a result, a classic data race occurred, discovered only after release.
Story
A developer implemented a newtype wrapper around Mutex but mistakenly made it !Sync, not implementing Sync manually. This prevented the use of the type in a multithreaded context (like inside Arc<Mutex<T>>), as the compiler required Sync. Fixed by implementing unsafe impl Sync and reviewing safety.