the Send and Sync marker traits
In most languages, whether a value is safe to use across threads is a fact you keep in your head and a comment, and getting it wrong gives you a data race that shows up only sometimes, under load, in production. Rust pulls that fact into the type system with two marker traits, Send and Sync. They have no methods — they are just labels the compiler attaches to a type to record 'this type is safe to move to another thread' (Send) or 'this type is safe to share between threads' (Sync). With them, the compiler can refuse to compile code that would send the wrong thing across a thread boundary.
Precisely: a type is Send if it is safe to transfer ownership of a value of that type to another thread (to MOVE it across). A type is Sync if it is safe for multiple threads to hold shared references (&T) to the same value at once — equivalently, T is Sync exactly when &T is Send. Most types are both, automatically. These are 'auto traits': the compiler implements them for your struct for free if every field is Send/Sync, propagating the property structurally, so you almost never write Send or Sync yourself. A few types deliberately are not. Rc<T> (the single-thread reference count) is neither Send nor Sync, because its count is updated without synchronisation and racing on it would corrupt it — so the compiler simply refuses to let an Rc cross a thread boundary, and you reach for Arc instead. A raw pointer is not Send/Sync either, which is why wrapping unsynchronised shared state and trying to share it fails to compile. The thread-spawning APIs require their closure (and everything it captures) to be Send, which is the checkpoint where all this is enforced.
Why this matters: Send and Sync are how 'fearless concurrency' actually works — the absence of data races in safe Rust is not a promise of good behaviour, it is a compile-time consequence of these two traits combined with the aliasing-XOR-mutability rule. The honest caveats: marker traits are about data races specifically, not deadlocks or logic races, so Send/Sync do not stop you writing a program that deadlocks. And because they are auto traits derived from a type's contents, a single non-Send field (like an Rc buried inside) makes the whole struct non-Send — which is correct, but can be a puzzling compile error until you trace which field is the culprit. For unsafe code you can implement them by hand with unsafe impl Send, and then YOU are promising the thread-safety the compiler could not verify.
use std::rc::Rc; use std::sync::Arc; use std::thread; let shared = Arc::new(42); // Arc IS Send + Sync let a = Arc::clone(&shared); thread::spawn(move || println!("{a}")).join().unwrap(); // OK let local = Rc::new(42); // Rc is NEITHER Send nor Sync // thread::spawn(move || println!("{local}")); // error[E0277]: `Rc<i32>` cannot be sent between threads safely
Send/Sync are auto-derived from a type's fields; one non-Send field (like an Rc) makes the whole type non-Send, and the spawn API enforces it.
Send/Sync prevent data races, not deadlocks or logic races. They are auto-derived, so a single non-thread-safe field disqualifies the whole type. In unsafe code you may write unsafe impl Send, but then you are personally vouching for the safety the compiler cannot check.