Apparently I've been playing a game with myself to see how many times I can answer my own questions in the process of figuring how to phrase them correctly enough to be understandable. Regardless, I still haven't found a firm 'does this exist' in my efforts this time, so I thought it might be worth posting to ask.
The thought is that it would be convenient for my purposes to have a struct, say StateVector, that holds some arbitrary number of values or sub structures, that:
There will only ever be one instance of StateVector in existence for the life of the program.
The sole instance of StateVector is constructed at runtime, so any reference to it will either be None (if referenced before it is constructed) or a valid StateVector.
The instance is mutable.
The instance is program scope level.
Such that a function definition could reference the StateVector instance without requiring it as a parameter, something like (hand-waving error handling):
Const and static seemed to be the closest I could dig up in a short amount of time, but const looks to be compile-time determined, and static looks like it has issues with mutability. Or function definitions can't reference things not declared in their parameters, and I've wandered out into imaginary weeds.
However, something you should keep in mind for this type of application is designing StateVector so that it provides interior mutability (by containing a mutex, atomics, or whatever else it needs), in which case you never use a &mut StateVector and you can make the static contain a OnceLock again. The advantage of putting the interior mutability inside the type is that it can usually offer an API which is more convenient to use, deadlock-proof, and maybe even more efficient. But I can’t show you an example of how to do this without knowing more about what StateVector actually is.
Dang, fast and thorough answer, thank you. I hadn't considered mutex in a single-thread context.
So if I'm understanding, Static hates being mutable, because reading a mutable static is considered unsafe (which seems reasonable), but Mutex/OnceLock/RwLock gets around that by having the type used in Static presented as immutable while interior mutability allows it to be changed in a controlled manner during runtime?
I apologize for the mess, but is this along the lines of what you're saying about making the type itself implement the interior mutability:
I recommend not thinking about it as “getting around”.
In order to have safe, global, mutable state, something needs to control access to that state to ensure that no data race, other memory unsafety, or higher-level misbehavior occurs. When you add a Mutex or OnceLock you are choosing what type of access control (or synchronization, which is where the name std::sync comes from) you want. It’s not built into the static itself because Rust is giving you the choice of what behaviors you want and what costs you are willing to pay.
(A tangential reply about how to think about Rust, not specific to your particular question.)
I recommend thinking about Rust in exclusive or shared terms, instead of immutable or mutable. For example, a &mut T is an exclusive reference to the T, and a &T is a shared reference to a T. The language relies on the exclusivity of &mut T to such an extent that it's considered UB to have an active &mut _ and &_ to the same memory.
With that model, a better term than "interior mutability" is "shared mutability".
Any part of a program can safely get a &_ to a static at any time,[1] so getting a &mut _ to a static cannot be a safe operation. Note how being single threaded isn't an escape hatch here. For example, holding a &mut _ to a static across a function call which gets a &_ to that same static further down the call stack is still UB. (Also consider things like signals.)
Or in other words, statics are intrinsically shared, so you need to have something that allows shared mutability (such as a Mutex) if you need to mutate something in a static.
Also FYI, when you use a const, it's like you pasted the definition into the use site -- every use is a new value, not a reference to some global value.
const V: Vec<u32> = Vec::new();
fn main() {
let mut a = V;
a.push(0);
let mut b = V;
b.push(1);
println!("{:?}", (a, b));
// ([0], [1])
}
Understood, appreciate the clarification and the reading. It does make more sense for static to lack its own inherent implementation of access control when the issue of shared/unique access is universal, and Mutex/OnceLock/etc are tools to address that broadly. And I'm about a quarter of the way through the article, but the framing its presenting is extremely helpful.
I will not use this to be lazy and write unreadable code. I will not use this to be lazy and write unreadable code. I will not use this to be lazy and write unreadable code.
It's entirely possible it is, I'm not familiar with what a singelton is.