Course contentsShow
← Rust
Lesson 187 of 1,9102. The Ownership ModelFree lesson

Managing Ownership in Global Variables

Use safe primitives to wrap global mutable state without violating ownership rules.

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

  1. The Wrapper: The Mutex owns the i32. When the program starts, the Mutex is initialized.
  2. Controlled Access: You cannot change the i32 directly. You must ask the Mutex for permission via .lock().
  3. 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.