22.4Send and Sync
In the last three lessons the compiler quietly made some choices for you. It let you move a Vec into a thread, but it would have rejected an Rc. It let you share an Arc<Mutex<T>> across threads, but not a bare Mutex wrapped in Rc. How does it know? The answer is two marker traits, Send and Sync, that classify every type by what's safe to do with it across threads. You'll almost never write them yourself; understanding them is about understanding what the compiler is actually checking when it makes concurrency "fearless."
Marker traits
A marker trait is a trait with no methods. It doesn't add behavior; it just marks a type as having some property, a label the compiler can check. Send and Sync are the two markers that matter for threads:
A type is Send if it's safe to move to another thread, to transfer ownership across a thread boundary. Most types are Send. If a thread can take ownership of a value, that value is Send.
A type is Sync if it's safe to share between threads by reference, if &T can be accessed from multiple threads at once. A type T is Sync exactly when &T is Send.
The distinction is move-it versus share-a-reference-to-it. When you move a Vec into a thread (lesson 22.1), the compiler is checking that Vec is Send. When you share an Arc<Mutex<T>> across threads, it's checking the relevant Sync/Send bounds. These checks are why thread::spawn and the channel and mutex types have the bounds they do; the safety isn't enforced by runtime checks but by which traits your types implement.
Almost everything is Send and Sync, automatically
Here's the part that makes this painless: Send and Sync are auto traits. The compiler implements them for your types automatically, based on their fields. A struct is Send if all its fields are Send; it's Sync if all its fields are Sync. You implement neither by hand. So when you write:
struct Point {
x: i32,
y: i32,
}
Point is automatically both Send and Sync, because i32 is both, and so a Point drops into a thread or an Arc with no ceremony. The vast majority of types you'll ever write are Send and Sync for free, which is why you've been doing concurrency for three lessons without once thinking about these traits. They were working silently the whole time.
The exceptions tell the story
The interesting cases are the few types that aren't Send or Sync, because each non-implementation is the compiler encoding a real danger you've already met.
Rc<T> is not Send. Recall from lesson 21.5 why: its reference count is updated non-atomically, so two threads cloning or dropping the same Rc at once could corrupt the count, a data race on the counter itself. Because Rc isn't Send, the compiler refuses to let it cross a thread boundary, and that's the actual mechanism behind "use Arc for threads." Arc is Send and Sync (its count is atomic), so it passes the check Rc fails. The advice "use Arc across threads" is really "use the type that's Send."
RefCell<T> is Send but not Sync. Its runtime borrow tracking (lesson 21.6) isn't safe against concurrent access, so sharing &RefCell across threads could let two threads both pass the borrow check and race. Not being Sync means the compiler won't let you share it. Mutex<T>, by contrast, is Sync (its locking is thread-safe), which is exactly why Mutex is the type you share across threads and RefCell is the one you don't.
See the pattern? Every "use this type, not that one for threads" rule from chapter 21 is really a Send/Sync fact. The traits aren't extra rules layered on top; they're the single underlying rule that generates all those pieces of advice.
Key insight
"Fearless concurrency" is Send and Sync doing their job at compile time. The compiler computes these traits automatically from your types' fields, and thread::spawn, channels, and Arc/Mutex carry bounds requiring them. So a type that would be unsafe to share, an Rc with its racy counter, a RefCell with its racy borrow flag, simply fails to compile when you try to thread it, with a "cannot be sent between threads safely" error. You don't audit for thread safety; the type system did it before the program ran. This is the guarantee no mainstream language before Rust offered.
When you'd see this
You won't write impl Send or impl Sync in normal code. Where these traits surface is in error messages. When you accidentally try to move a non-Send type into a thread, the compiler says something like `Rc<i32>` cannot be sent between threads safely and points at the Send bound on spawn. Now you'll recognize what it's telling you: this type lacks the marker that certifies it safe, switch to the thread-safe equivalent (Rc to Arc, RefCell to Mutex). The fix is almost always swapping in the Send/Sync version of the tool, the exact swaps the last two lessons taught.
For advanced readers
You can implement Send/Sync by hand, but only inside unsafe (chapter 25), because you're then promising the compiler something it couldn't verify, that your type's internal sharing really is safe. This comes up when wrapping raw pointers or foreign resources where you've manually ensured thread safety. It's rare, expert territory, and the burden of proof is entirely on you. For all ordinary code, the automatic derivation is correct and you should never need to override it.
Quiz time
Question #1
What's the difference between Send and Sync?
Show solution
Send means a type is safe to move (transfer ownership) to another thread. Sync means a type is safe to share between threads by reference, i.e. &T can be accessed from multiple threads at once (formally, T is Sync iff &T is Send). Move-it versus share-a-reference-to-it. Both are marker traits (no methods) that the compiler uses to decide what thread operations are allowed.
Question #2
Do you usually implement Send and Sync yourself? How does a type get them?
Show solution
No. They're auto traits: the compiler implements them automatically for a type when all of its fields are Send/Sync. So almost every type you write is both, for free, which is why everyday concurrency code never mentions them. You'd only implement them manually inside unsafe for exotic types (e.g. wrapping raw pointers), which is rare expert work.
Question #3
Rc<T> is not Send. How does that single fact explain the rule "use Arc, not Rc, across threads"?
Show solution
Because thread::spawn (and friends) require the moved data to be Send, and Rc isn't Send (its non-atomic reference count could be corrupted by concurrent access), the compiler rejects any attempt to move an Rc into another thread, that's the actual mechanism behind the advice. Arc is Send/Sync because its count is atomic, so it passes the bound Rc fails. The "use Arc" rule is just "use the type that satisfies the Send requirement."
You now understand both how to do concurrency safely and why the compiler can guarantee it. The final lesson before the quiz (22.5) shows the ecosystem's victory lap: the rayon crate, which turns a sequential iterator into a parallel one by changing a single method name.