In this lesson, you will learn how to safely share and mutate global state in Rust without violating the strict rules of ownership.
The Problem with Globals
In many languages, you can create a global variable and modify it from anywhere. In Rust, this is dangerous because the compiler cannot track who owns that data or if multiple parts of your program are trying to write to it simultaneously, leading to data races. Since global variables must exist for the entire duration of your program, they require a "static" lifetime.
Wrapping for Safety
To manage global state while respecting ownership, we use Safe Primitives. We don't store raw data in a global; instead, we store the data inside a container that provides internal mutability and thread safety.
The standard pattern is to use static variables paired with Mutex (for mutual exclusion) or RwLock (for read-write locking). These wrap your data, ensuring that even though the variable is global, the access to that data is strictly controlled.
Consider this example using std::sync::Mutex:
use std::sync::Mutex;
// We use a static variable with a Mutex to provide safe shared access
static GLOBAL_COUNTER: Mutex<i32> = Mutex::new(0);
fn increment_counter() {
// We "lock" the mutex to get access to the data inside
let mut data = GLOBAL_COUNTER.lock().unwrap();
*data += 1;
// The lock is automatically dropped when 'data' goes out of scope
}
Why This Works
- The Wrapper: The
Mutexowns thei32. When the program starts, theMutexis initialized. - Controlled Access: You cannot change the
i32directly. You must ask theMutexfor permission via.lock(). - Ownership Hand-off: The lock returns a guard that acts like a smart pointer. When that guard goes out of scope, the lock is released, and the "ownership" of the ability to modify the data is returned to the
Mutex.
By using these primitives, you ensure that even though the variable is global, the memory safety rules are enforced at runtime, preventing conflicting access.
Key Takeaway: Never store raw data in a global variable; wrap it in synchronization primitives like Mutex to enforce safe, serialized access through Rust's ownership rules.