Course contentsShow
← Rust
Lesson 260 of 1,9103. Borrowing and ReferencesFree lesson

Managing Global State with References

Use thread-safe globals safely through strict reference management.

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.