#Easiest possible mutable global variables in rust

100 messages · Page 1 of 1 (latest)

half gorge
#

we have Mutex (its really difficult to handle).

I need a way to create these in a way like:

let mut x = 12;

fn something(){
    // use x here as well
}

fn main(){
    // use x here
}

we have static mut but its unsafe.

drifting nacelle
#

use a Mutex or some other thread-safe interior mutable type (like RwLock)

half gorge
#

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 😵‍💫

latent tundra
#

Also the x looks like you want a static

half gorge
latent tundra
half gorge
#

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!

gleaming jungle
#

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.

drifting nacelle
fathom bridge
#

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)

drifting nacelle
fathom bridge
#

Whoops typo

forest merlin
#

What is your global state for? If it's one time initialization, you can use the once_cell crate

sullen igloo
#

Read Smart Pointer chapter on the Rust Programming book

gaunt nimbus
#

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?

forest merlin
#

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

gaunt nimbus
#

Am I completely wrong on the usage of global variables?

forest merlin
#

I don't think "it's tedious" is sufficient justification for using global variables...

gaunt nimbus
#

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"?

forest merlin
#

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

gaunt nimbus
#

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...

forest merlin
gaunt nimbus
#

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

forest merlin
#

And functions can unsafely turn that into a &T

gaunt nimbus
#

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 👀

forest merlin
#

You could enforce order of functions, but doing so requires passing around something that represents "this thing is already called"

gaunt nimbus
#

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

half gorge
#

Thanks alot everyone! I learn lots of things from your conversations 😄

gilded spoke
#

libraries that forget that users may want state have been a problem for decades in numerous languages, this is not rust-specific at all.

gleaming jungle
#

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

gilded spoke
#

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.

gleaming jungle
#

eh, you can have a memory barrier in main and ensure it that way

gilded spoke
#

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

gleaming jungle
#

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);
}
bright questBOT
#
get_static: 1.1ns ± 0.4
get_lazy: 1.4ns ± 0.6```
gleaming jungle
#

easily within the variance

gleaming jungle
#

the problem to me is mostly when you have cycles between pre-main-initialized globals

gilded spoke
#

You can even call it yourself

#

It works

#

Making it Weird And Special just does not seem very useful

gleaming jungle
#

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

gilded spoke
gleaming jungle
#

are there platforms that directly call main?

#

I kinda doubt that

gilded spoke
#

There are platforms where main itself doesn't exist, there isn't a portable entrypoint function.

gleaming jungle
#

yep, but that's only on no_std

gilded spoke
#

The closest thing is main itself, because the compiler does the portability below you

gleaming jungle
#

right?

gilded spoke
#

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

gleaming jungle
#

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

gilded spoke
#

It's exactly as magic as main is: It's called once by an external force

gleaming jungle
#

but _start is already magic to most users

gilded spoke
#

As is main.

gleaming jungle
#

i guess main is too

gilded spoke
#

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

gleaming jungle
#

yes

gilded spoke
#

I suppose it solves the static initialization order fiasco by making you write static initialization

gleaming jungle
#

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

gilded spoke
#

If you're going to go with effects, we can generalize even further

gleaming jungle
#

true, we can just encode "variable x is initialized" with its own effect

gilded spoke
#

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

gleaming jungle
#

it feels like this would eventually boil down to being equivalent to passing arguments to functions

gilded spoke
#

That's because it is

#

It's more or less dynamic scoping, which can be emulated by just passing down sufficient amounts of context