Why Rust forces you to choose between sharing and mutating.
Every data race in history requires exactly two ingredients: Sharing (two threads can access the data) and Mutation (at least one thread changes it). In C++ or Java, it's easy to accidentally share mutable data, relying on developers to remember to add locks. Rust's compiler literally makes it illegal to compile code that has both Sharing and Mutation without explicit, safe wrappers.
If you want to share data across threads in Rust, you wrap it in an Arc (Atomic Reference Count). This gives you Shared Immutable state. If you also need to modify it, you must wrap the data in a Mutex inside that Arc. The compiler forces you to acquire the lock before accessing the data. If you don't, it will not compile.
// RUST EXAMPLE
use std::sync::{Arc, Mutex};
use std::thread;
// 1. Mutex gives us Mutability (safe mutation).
// 2. Arc gives us Sharing (safe cross-thread references).
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
// Clone the Arc to get a reference for the new thread
let counter_ref = Arc::clone(&counter);
let handle = thread::spawn(move || {
// COMPILER ERROR if you try to mutate counter_ref directly here!
// You MUST acquire the lock to get the mutable data inside
let mut num = counter_ref.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
The cost is developer friction. Rust's "Fearless Concurrency" means you will spend time fighting the borrow checker to prove your code is safe. The benefit is that if your multi-threaded Rust code compiles, it is mathematically guaranteed to be free of Data Races.