#Easiest possible mutable global variables in rust
100 messages · Page 1 of 1 (latest)
use a Mutex or some other thread-safe interior mutable type (like RwLock)
Oh! let me Rwlock
oh no, even that's complex. Don't think it itself is complex. But putting this in a ccode that is already very complex would be 😵💫
Also the x looks like you want a static
oh, yes 😓
This is what you get for having global state 
yes
ok, i'll use one out of Mutex or Rwlock ||or maybe make my own||
I think even mutex is made up of something:
declared as unsafe
but logically very safe
Thanks a lot everyone for help!
If it's just for integers like in your example, you could also look at using atomics, which may be more straightforward to use. But otherwise, probably just a Mutex, yeah.
only safe if you uphold the invariants yourself
You generally want to avoid global mutable safe altogether, if you need it there's likely something wrong with your design
Rust tends to nudge (or shove) you into the pit of success (like my current project: it forced me to make my API better)
mutable, but yes, that too
Whoops typo
What is your global state for? If it's one time initialization, you can use the once_cell crate
UnsafeCell isn't logically safe. https://manishearth.github.io/blog/2015/05/17/the-problem-with-shared-mutability/
Read Smart Pointer chapter on the Rust Programming book
Oh I would like to hop on this thread and continue the topic: Yes, I personally know that the solution to one time initialization is once_cell, and I also have read that "problem with single threaded shared mutablity" blog several times. Still, I have one lingering question:
(Given single thread context) Is there just no way in Rust to express to the compiler that, an variable X is only written before this A point in main(), and not written past A? Seems like becase we can't express this, once_cell always have a cost of checking if self is initialized? Is this the same type of acceptable runtime costs as in RefCell? Or we are so sure that LLVM will do the optimization and remove the initialized? check?
The way to do that is to create a new variable that's immutable, possibly shadowing the old one. Then, pass around references to that variable.
The new variable probably isn't even needed, probably.
If you want to store the variable in a static, you're sure that your application logic will initialize the thing before you use it, and you're willing to use unsafe, then you'll need MaybeUninit
I don't really understand this under this topic. Like we use global variables when passing one variable all the way down to the call site is tedious
If we could pass the variable we are interested in all the way down, then yes, borrow immutably and the compiler will know that during borrow, the variable is not written
Am I completely wrong on the usage of global variables?
I don't think "it's tedious" is sufficient justification for using global variables...
seems like OP never tells about their exact usage either...
so what are the intended usage for once_cells? According to the docs they are for one-time initialization, lazy initialization... I thought they are used when several parties need a same piece of data, however it is hard to pass the data from main to them. Isn't such hardship only about "tediousness"?
It's for when you want everyone to use the same thing, in a once-per-program way.
As in, you don't want more instances of this thing to exist, kinda.
Basically it's when you want to use a global immutable thing, but a const/static can't hold the thing you want due to intialization issues
so how exactly does your
The way to do that is to create a new variable that's immutable, possibly shadowing the old one. Then, pass around references to that variable.
approach work for such scenarios?
And you also mean that even without such an approach, the later reads after initialization early in code won't have runtime costs after compiled?
Like for sync::once_cell there is even one atomic flag access, which is usually hundreds of cycles of CPU
If there are mutliple threads again and again reading such initialized values, all with a initialized? check, I think we can't really say the cost is negligiable...
I literally meant something like just
let thing = create_thing();
use_thing(&thing);
use_thing_again(&thing);
And I mean, if this is possible for the use case, one won't even bother to use global variables... right?
If the use site is able to access thing
Yeah.
Like, rust doesn't have a way to guarantee at compile time that a function will be called only after some initialization is done.
You can always do a static of UnsafeCell<MaybeUninit<T>>, if you want to ensure that yourself.
And functions can unsafely turn that into a &T
I will be happy to receive a definite answer of "The thing you want to tell to the compiler is not expressible with safe Rust", so I will just take the conclusion and stop trying
If you are confident enough I will take this conclusion 👀
Yeah, what you're trying to express (order in which functions must be called) is too complicated to be expressed in rust's type system.
You could enforce order of functions, but doing so requires passing around something that represents "this thing is already called"
Well this is where the "tedious" argument comes from
What I have in my mind is, if one is really into performance and writes everything from main() themself, there is probably no need for global vars
Just like, if one bothers to reason all the interleaving of functions and invariants of large struct, one won't need RefCells, regular borrow checks will do
However, sometimes the mental burden is too high, so people use those cells, at the cost of runtime costs
And not only about "laziness", sometimes hard to pass variables around after depending on other crates, for example a web server framework that spawns thread for the user
it would be unclear how to pass a certain stuff in main to a worker context, if the framework is not considering this case
Thanks alot everyone! I learn lots of things from your conversations 😄
it would be unclear how to pass a certain stuff in main to a worker context, if the framework is not considering this case
It would, but that would be a problem with the framework, not with anything else.
libraries that forget that users may want state have been a problem for decades in numerous languages, this is not rust-specific at all.
also reminder that using a OnceLock or Mutex really isn't that expensive.
you're checking one boolean atomically with acquire semantics, and it's already in the correct location in cache
Incidentally, forcing a given function to run before another function without runtime checks would require some way to track which functions have been ran statically, which means you need your framework to support this tracking.
If it can do that, it can just have a state parameter instead.
eh, you can have a memory barrier in main and ensure it that way
You can, but you can't compile-time-check this.
Unless your argument is to make main itself special, which will annoy a whole lot of libraries and not actually help most binaries that much
yeah ofc, not compile time checked
here's something quick&dirty:
?microbench
#![feature(lazy_cell)]
use std::sync::LazyLock;
static mut STATIC: u64 = 42;
pub fn get_static() {
std::hint::black_box(unsafe { STATIC });
}
static LOCK: LazyLock<u64> = LazyLock::new(|| { 42 });
pub fn get_lazy() {
std::hint::black_box(*LOCK);
}
get_static: 1.1ns ± 0.4
get_lazy: 1.4ns ± 0.6```
easily within the variance
i mean, there's already some special bootstrapping code somewhere that's calling main, so I don't think this is a super strong argument on its own
the problem to me is mostly when you have cycles between pre-main-initialized globals
main is just a function, even if it's one that is called by an external runtime
You can even call it yourself
It works
Making it Weird And Special just does not seem very useful
that's... exactly my point
there's already a special start function that you could abuse for this
not that I'd recommend it, because there's other issues
Not on all platforms there isn't.
There are platforms where main itself doesn't exist, there isn't a portable entrypoint function.
yep, but that's only on no_std
The closest thing is main itself, because the compiler does the portability below you
right?
So you might as well just special-case main.
Kicking the can down the road to "the first function anyone writes" doesn't seem very helpful
When "the first function that is considered part of the language by specification" is just there
Still not a fan, though. What you kinda want is an init function that runs beginning-to-end immediately before main, and has some kind of special language support such that statics initialized in init may be considered initialized by the rest of the program
the point is that _start et al are already provided by std, so you could do some magic there without uipsetting people that main is magic
yes, I'm not a fan either
You will instead upset people the _start is magic
It's exactly as magic as main is: It's called once by an external force
but _start is already magic to most users
As is main.
i guess main is too
Either way, though, "statics initialized in main are initialized everywhere" is insufficient, one wants "statics initialized in main before any non-main code runs are initialized everywhere"
Aka init
yes
I suppose it solves the static initialization order fiasco by making you write static initialization
you know
i suppose
effects, anyone?
may-use-statics effect?
that could actually work lol
I've watched like one presentation about effects, and now I see them everwhere
If you're going to go with effects, we can generalize even further
true, we can just encode "variable x is initialized" with its own effect
Replace statics entirely with an effect that asks a handler somewhere above you for a variable. You can now implement statics by putting a handler around main.
And we can also "intercept" static access, so the traditional issues with how statics prevent you from un-singletoning your code do not apply
it feels like this would eventually boil down to being equivalent to passing arguments to functions