Global variable


#1

Say I have a commandline app where I could specify an option --myopt which turns something on.
I want to have that option available in all my functions in main.rs.

The only way I found it using a static mutable variable and using unsafe at two places.

#[derive(Debug)]
struct Global {
    myopt: bool
}

static mut GLOBAL: Global = Global{ myopt:  false };

impl Global {
    fn set(&mut self, value: bool) {
        self.myopt = value;
    }

    fn get(&self) -> bool {
        self.myopt
    }
}


macro_rules! set_myopt {
    ($fmt:expr) => (
        unsafe {
            GLOBAL.set($fmt)
        }
    );
}

macro_rules! get_myopt {
    () => (
        unsafe {
            GLOBAL.get()
        }
    );
}

fn main() {
    println!("Show default setting of myopt: {:?}", get_myopt!());
    let myopt = true;  // assume gotten from cmdline
    // activate what we got from cmdline
    set_myopt!(myopt);
    println!("Show active setting of myopt: {:?}", get_myopt!());
}

I’m not exactly a fan of unsafe. So, my question: how could I solve it in a better way?

Thanks.


Manfred


#2

Why do you need mutable access to this variable everywhere in your program? Convinient way to solve this problem is to create the configuration struct (Global in your case, but i would rename it to smthng like Config) on very beginning of program, and then pass it wherever its needed. Global mutable state, however achieved, is in general an evil idea.


#3

Sure Config is better. Global was just a name given in the minimal example.

I find it just inconvenient to pass Config around as I change it (if the option was specified at the command line) a single time at program start.


#4

It may looks inconvinient on the beggining, but later it safes you from real problems. Answering initial question: you can achieve this using thread_local crate, or lazy_static+Mutex.


#5

The main problem with mutable statics is that they are only memory-safe in the right usage conditions. Set them and read them concurrently from two different threads and you get a data race. This dependency on correct usage patterns is the very defining characteristic of unsafe in Rust, so hiding the unsafety through encapsulation as you are doing here is not correct.

Some alternate solutions, in orders of decreasing preference:

  • Pass configuration state explicitly. => Most idiomatic, avoids global variable trouble
  • Synchronize concurrent access to global mutable state (with Mutex, AtomicPtr…) => Has a performance and complexity cost
  • Use thread-local state => I don’t think that is appropriate for your use case as you probably want to set the configuration in an application-wide fasion.
  • Expose and use carefully an unsafe interface => If you are exposing an interface which can blow up when used incorrectly, this is the right thing to do.

#6

lazy_static is your best bet if you want to stick with a global: example


#7

I think for me the only viable solutions are one of the following

  1. Use unsafe as in my example as I only set the variable once in the beginning of the program. So this is kind of a mitigation.
  2. Pass a Config struct around
  3. Use lazy_static

Thanks to all of you. Best, Manfred


#8

But in the end I assume that lazy_static also has to use unsafe.


#9

It does, but you can/should assume they got it right :slight_smile:. It uses std::sync::Once internally, IIRC.


#10

Note that an abstraction that uses unsafe internally is not problematic in and of itself. Otherwise, you wouldn’t even be able to use the std library.

What is unacceptable is exposing an interface which pretends to be safe, but can break type/memory/thread-safety if used in the wrong way. Or, in more advanced usage scenarios, exposing an unsafe interface without documenting under which assumptions this interface is safe.

Unsafe in code means “Dear compiler, please let me do some things which are potentially memory/type/thread-unsafe”, whereas unsafe in interfaces means “Dear user, this interface is only safe if specific precautions are taken when using it, please be careful with that contract”.


#11

I would say this is actually a good thing. By having to explicitly pass in the information you need it’s very easy to see which parts of an application depend on what. It also tends to make testing and maintaining things a lot easier in the long run (e.g. with dependency injection and all that).

I’ve spent quite a long time writing Rust code and then when I started working on a C# application at work I found that having globals is a great way to accidentally make a tightly coupled ball of spaghetti.

I originally started programming using Python and when I saw Rust forcing you to stop using globals my reaction was quite similar. My advice would be to give it a try and see how your application works out by populating your Config struct in main() then passing references to the bits of code which need it.


#12

While it’s valuable and important to understand why Rust makes globals a PITA, it’s also important to acknowledge that sometimes they’re useful, particularly when the application is small/immature. That’s partly the reason crates like lazy_static exist in the first place. Once the application reaches a certain size, it’s possible to refactor away from the global if it becomes a problem.

So we should be pragmatic, rather than dogmatic :slight_smile:.


#13

It’s important to note that if all you need is a single Boolean, lazy static is overkill. Use an atomic Boolean!


#14

In some ways, yes. But AtomicBool has different semantics - code can toggle it as many times as it wants, whereas lazy_static is an init-once semantic.


#15

In my case the single bool was the minimal example. In my real cmdline app I (currently) have 9 options.

8 of them I could easily pass around as they were used only in 1 or 2 functions. The 9th was the beast which I really need in many places as that option --no-colors tells the application to not use colored crate when issuing messages.


#16

A command line app with many options is the only situation where till now I sometimes felt a need to use the config struct globally.


#17

Ah, that’s fair.