In this lesson, you will learn how to safely share global state across threads using reference management and the Sync trait constraints.
The Challenge of Global State
In Rust, global variables are inherently risky because they exist for the entire duration of your program—the 'static lifetime. Because globals are accessible from anywhere in your code, the compiler cannot automatically track who is reading or writing to them. If you try to share a global variable across multiple threads, you run the risk of data races. To prevent this, Rust requires that any global data shared across threads must satisfy the Sync trait, which guarantees that it is safe for multiple threads to hold references to it simultaneously.
Using References to Wrap Globals
To manage global state safely, we avoid global mutable variables. Instead, we use a global constant that holds a reference or a thread-safe wrapper. A common pattern is to define a global "manager" that encapsulates the state and exposes it through shared references.
use std::sync::Mutex;
// We use a static constant to represent our global state.
// The Mutex provides interior mutability, and 'Sync' ensures thread safety.
static GLOBAL_CONFIG: Mutex<i32> = Mutex::new(0);
fn update_config(new_val: i32) {
// We get a reference to the data inside the Mutex.
// The borrow checker ensures that only one thread can modify it at a time.
let mut data = GLOBAL_CONFIG.lock().unwrap();
*data = new_val;
}
fn main() {
update_config(42);
println!("Config value: {:?}", GLOBAL_CONFIG.lock().unwrap());
}
Why This Works
The Mutex acts as a guard. When you call .lock(), you obtain a reference that is managed by the borrow checker. Because the Mutex is Sync, you can safely pass references to it across thread boundaries. The compiler uses the rules of Send and Sync (which you have already studied) to verify that your global reference cannot cause memory unsafety. By forcing all access through these managed references, you turn potentially chaotic global access into a predictable, borrow-checked flow.
Key Takeaway: Manage global state by wrapping it in thread-safe, sync-compatible primitives that enforce borrow-checked access, ensuring that even globally reachable data obeys Rust’s strict aliasing rules.