You learned ownership. You learned borrowing. You know that Rust won't let you mutate data while someone's reading it.

But here's the thing: those rules don't just prevent memory bugs. They prevent concurrency bugs.

What is a Data Race?

A data race happens when two threads access the same memory at the same time, and at least one of them is writing. The result? Undefined. Garbage. Bugs that appear randomly and vanish when you add logging.

Most languages solve this at runtime. You add locks. You add mutexes. You cross your fingers and hope you didn't forget one.

Rust solves it at compile time.

Enter: Send and Sync

These are two marker traits that Rust uses to enforce concurrency safety:

They're not about what your code does. They're about what your type allows.

The Magic Rule

// If &T is Send, then T is Sync
// And vice versa — if &T is Sync, then T is Send

That's it. If you can safely share an immutable reference to your type between threads, your type is safe to share itself.

What Actually Happens

When you try to spawn a thread:

std::thread::spawn(move || {
    do_something(my_data);
});

Rust checks: Is my_data Send?

If it's not — if it's a Rc<T> or a RefCell<T> or any type that doesn't support concurrent access — the compiler says no. Right at compile time. Before you even run it.

Why This Matters

In other languages, you discover concurrency bugs at 3am when your production server hangs. In Rust, you discover them when you're writing code.

The borrow checker isn't just checking memory safety. It's checking thread safety.

Compare to Go

Go gives you goroutines and channels. Beautiful. Simple. But if two goroutines mutate the same data without a mutex? Race condition. You need go test -race to catch it.

Compare to Erlang

Erlang's actors are isolated — each process has its own memory. Messages are copied. This is safe, but it means every piece of data gets copied across process boundaries. Rust lets you choose: move it (Send), share it (Arc + Sync), or copy it.

The Counterexample

Not everything is Send or Sync. The classic example:

use std::rc::Rc;

let data = Rc::new(vec![1, 2, 3]);
// Can't spawn a thread with this!
// Rc is not Send — its reference counting is not thread-safe

Why? Because Rc uses non-atomic reference counting. If two threads increment the counter simultaneously, you get corrupted memory. Rust says: nope.

The fix? Arc (atomic reference counting):

use std::sync::Arc;

let data = Arc::new(vec![1, 2, 3]);
// Now this is fine — Arc is Send + Sync

The Bigger Picture

Ownership was never just about memory. It's a general principle:

If you control who can access data and when, you control correctness.

The borrow checker applies this principle everywhere — whether you're passing a reference to a function or passing ownership to a thread. The same rules keep you safe in both cases.

This is why Rust can claim "fearless concurrency." It's not a marketing slogan. It's a consequence of the ownership model.


Next: when you need shared mutable state across threads, reach for Arc<Mutex<T>>. But that's a whole other post.