25.5 Accessing and Modifying Mutable Static Variables

Rust supports global variables declared with the static keyword. By default, static variables are immutable and must be initialized with constant expressions. To allow mutable global state, Rust provides static mut:

// Mutable static variable. Initialization must be a constant expression.
static mut GLOBAL_COUNTER: u32 = 0;

fn increment_global_counter() {
    // Accessing (reading or writing) a `static mut` is unsafe.
    unsafe {
        GLOBAL_COUNTER += 1;
    }
}

fn read_global_counter() -> u32 {
    // Reading is also unsafe.
    unsafe {
        GLOBAL_COUNTER
    }
}

fn main() {
    increment_global_counter();
    increment_global_counter();
    // Need unsafe block even for read access via function.
    println!("Counter value: {}", read_global_counter()); // Outputs 2
}

Accessing static mut variables is unsafe primarily because it introduces the risk of data races. If multiple threads access the same static mut variable concurrently, and at least one access is a write, without proper synchronization, the behavior is undefined. Rust’s compile-time safety guarantees cannot prevent data races involving static mut.

Comparison to C: This is directly analogous to mutable global variables in C, which are similarly susceptible to race conditions in multithreaded programs unless protected by external synchronization mechanisms (like mutexes).

Best Practice: Avoid static mut whenever possible. For mutable shared state, use safe concurrency primitives provided by the standard library:

  • std::sync::Mutex<T> or std::sync::RwLock<T>: Wrap the data in a lock to ensure exclusive access.
  • std::sync::atomic types (e.g., AtomicU32, AtomicBool, AtomicPtr): Provide atomic operations for lock-free updates on primitive types.
use std::sync::atomic::{AtomicU32, Ordering};

// Safe global counter using AtomicU32.
static SAFE_COUNTER: AtomicU32 = AtomicU32::new(0);

fn increment_safe_counter() {
    // fetch_add provides atomic increment. No `unsafe` needed.
    // Ordering specifies memory ordering constraints for concurrent access.
    SAFE_COUNTER.fetch_add(1, Ordering::SeqCst);
}

fn read_safe_counter() -> u32 {
    // load provides atomic read. No `unsafe` needed.
    SAFE_COUNTER.load(Ordering::SeqCst)
}

fn main() {
    increment_safe_counter();
    increment_safe_counter();
    println!("Safe counter value: {}", read_safe_counter()); // Outputs 2
}

These alternatives provide safe APIs for managing shared mutable state, leveraging Rust’s safety features even in concurrent contexts.