ProgrammingSystem Libraries Developer / Backend Developer

How does 'thread safety' (Send and Sync) work in Rust, and how can they be implemented/limited for user types?

Pass interviews with Hintsage AI assistant

Answer.

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.

Trick question.

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.

Examples of real errors due to ignorance of the intricacies of the topic.


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.