Global state in Rust compiles but doesn't work

I'm trying to save a variable in a global state. First I thought about using Cell, but then remembered it isn't Sync, so multiple threads accessing that global state could be catastrophic. Surely the compiler wouldn't compile that, right?

It would! This code compiles fine with no warnings: ( Rust Playground )

use std::cell::Cell;

pub const GLOBAL_STATE: Cell<u32> = Cell::new(0);

fn main() {
    GLOBAL_STATE.set(5);
    println!("{}", GLOBAL_STATE.get()); // Prints 0
    
    let x = Cell::new(0);
    x.set(5);
    println!("{}", x.get()); // Prints 5
}

How is this possible? Surely the compiles would not allow types that are not Sync into const like this? How is this not working at runtime better than a compiler error? I thought Rust was supposed to catch bugs like this at compile time. It's not even a compiler warning, although clippy does catch it.

I also tried a variant with Mutex. To my suprize, the same problem: the code compiles, but doesn't work at runtime: ( Rust Playground )

use std::sync::Mutex;

pub const GLOBAL_STATE: Mutex<u32> = Mutex::new(0);

fn main() {
    *GLOBAL_STATE.lock().unwrap() = 5;
    println!("{:?}", GLOBAL_STATE.lock()); // Prints 0
    
    let x = Mutex::new(0u32);
    *x.lock().unwrap() = 5;
    println!("{:?}", x.lock()); // Prints 5
}

A quick google search suggests using the lazy_static crate for global state, but my question is, how on earth does compile if it's going to be this bugged out at runtime?

EDIT:
I was thinking of static instead of const.
static GLOBAL_STATE: Cell<u32> = Cell::new(0); correctly doesn't compile, and static GLOBAL_STATE: Mutex<u32> = Mutex::new(0); correctly works as expected.

Still strange that const happily compiles and then doesn't work at runtime.

It's because a const isn't actually a global. Rather, its value is copied to every location where you are using it separately.

6 Likes

What you want is a static.

A constant item is an optionally named constant value which is not associated with a specific memory location in the program. Constants are essentially inlined wherever they are used, meaning that they are copied directly into the relevant context when used. This includes usage of constants from external crates, and non-Copy types. References to the same constant are not necessarily guaranteed to refer to the same memory address.
src: Constant items - The Rust Reference

A static item is similar to a constant, except that it represents a precise memory location in the program. All references to the static refer to the same memory location.
src: Static items - The Rust Reference


It's not a bug, since it's perfectly reasonable to call a method from a constant:

#[derive(PartialEq, Eq)]
struct Color([u8; 3]);

impl Color {
    const RED: Color = Color([255, 0, 0]);
    const GREEN: Color = Color([0, 255, 0]);

    fn red(&self) -> &'static str {
        match *self {
            Color::RED => "red",
            _ => "not red",
        }
    }
}

const RED: Color = Color::RED;

fn main() {
    println!("{}", RED.red());
    println!("{}", Color::GREEN.red());
}
3 Likes

There's definitely some interest in "uplifting" those Clippy lints into rustc. Probably just needs a champion to get it implemented and merged.

4 Likes

I found an interesting and reasonable case along that thread:

const INIT: Mutex<i32> = Mutex::new(0);
static INITS: [Mutex<i32>; 2] = [INIT; 2];
4 Likes

Maybe once const blocks are a thing rustc could lint against this but not against

static INITS: [Mutex<i32>; 2] = [const { Mutex::new() }; 2];
1 Like

It's a bug in my code, not in Rust.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.