312

What is the best way to create and use a struct with only one instantiation in the system? Yes, this is necessary, it is the OpenGL subsystem, and making multiple copies of this and passing it around everywhere would add confusion, rather than relieve it.

The singleton needs to be as efficient as possible. It doesn't seem possible to store an arbitrary object on the static area, as it contains a Vec with a destructor. The second option is to store an (unsafe) pointer on the static area, pointing to a heap allocated singleton. What is the most convenient and safest way to do this, while keeping syntax terse?

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
stevenkucera
  • 3,855
  • 2
  • 18
  • 13
  • 2
    Have you looked at how the existing Rust bindings for OpenGL handle this same problem? – Shepmaster Jan 06 '15 at 03:14
  • 31
    *Yes, this is necessary, it is the OpenGL subsystem, and making multiple copies of this and passing it around everywhere would add confusion, rather than relieve it.* => this is not the definition of *necessary*, it is maybe *convenient* (at first) but not necessary. – Matthieu M. Jan 06 '15 at 07:11
  • 3
    Yes you have a point. Although since OpenGL is a big state machine anyway, I am close to certain there will not be a clone of it anywhere, whose use would only result in OpenGL errors. – stevenkucera Jan 07 '15 at 00:37
  • 4
    @MatthieuM. It is necessary for convenience. – Asker Jan 08 '23 at 13:07

8 Answers8

416

Non-answer answer

Avoid global state in general. Instead, construct the object somewhere early (perhaps in main), then pass mutable references to that object into the places that need it. This will usually make your code easier to reason about and doesn't require as much bending over backwards.

Look hard at yourself in the mirror before deciding that you want global mutable variables. There are rare cases where it's useful, so that's why it's worth knowing how to do.

Still want to make one...?

Tips

In the 3 following solutions:

  • If you remove the Mutex then you have a global singleton without any mutability.
  • You can also use a RwLock instead of a Mutex to allow multiple concurrent readers.

Using lazy-static

The lazy-static crate can take away some of the drudgery of manually creating a singleton. Here is a global mutable vector:

use lazy_static::lazy_static; // 1.4.0
use std::sync::Mutex;

lazy_static! {
    static ref ARRAY: Mutex<Vec<u8>> = Mutex::new(vec![]);
}

fn do_a_call() {
    ARRAY.lock().unwrap().push(1);
}

fn main() {
    do_a_call();
    do_a_call();
    do_a_call();

    println!("called {}", ARRAY.lock().unwrap().len());
}

Using once_cell

The once_cell crate can take away some of the drudgery of manually creating a singleton. Here is a global mutable vector:

use once_cell::sync::Lazy; // 1.3.1
use std::sync::Mutex;

static ARRAY: Lazy<Mutex<Vec<u8>>> = Lazy::new(|| Mutex::new(vec![]));

fn do_a_call() {
    ARRAY.lock().unwrap().push(1);
}

fn main() {
    do_a_call();
    do_a_call();
    do_a_call();

    println!("called {}", ARRAY.lock().unwrap().len());
}

Using std::sync::LazyLock

The standard library is in the process of adding once_cell's functionality, currently called LazyLock:

#![feature(once_cell)] // 1.67.0-nightly
use std::sync::{LazyLock, Mutex};

static ARRAY: LazyLock<Mutex<Vec<u8>>> = LazyLock::new(|| Mutex::new(vec![]));

fn do_a_call() {
    ARRAY.lock().unwrap().push(1);
}

fn main() {
    do_a_call();
    do_a_call();
    do_a_call();

    println!("called {}", ARRAY.lock().unwrap().len());
}

Using std::sync::OnceLock

LazyLock is still unstable, but OnceLock was stabilized as of Rust 1.70.0. You can use it to get dependency-free implementation on stable:

use std::sync::{OnceLock, Mutex};

fn array() -> &'static Mutex<Vec<u8>> {
    static ARRAY: OnceLock<Mutex<Vec<u8>>> = OnceLock::new();
    ARRAY.get_or_init(|| Mutex::new(vec![]))
}

fn do_a_call() {
    array().lock().unwrap().push(1);
}

fn main() {
    do_a_call();
    do_a_call();
    do_a_call();

    println!("called {}", array().lock().unwrap().len());
}

A special case: atomics

If you only need to track an integer value, you can directly use an atomic:

use std::sync::atomic::{AtomicUsize, Ordering};

static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);

fn do_a_call() {
    CALL_COUNT.fetch_add(1, Ordering::SeqCst);
}

fn main() {
    do_a_call();
    do_a_call();
    do_a_call();

    println!("called {}", CALL_COUNT.load(Ordering::SeqCst));
}

Manual, dependency-free implementation

There are several existing implementation of statics, such as the Rust 1.0 implementation of stdin. This is the same idea adapted to modern Rust, such as the use of MaybeUninit to avoid allocations and unnecessary indirection. You should also look at the modern implementation of io::Lazy. I've commented inline with what each line does.

use std::sync::{Mutex, Once};
use std::time::Duration;
use std::{mem::MaybeUninit, thread};

struct SingletonReader {
    // Since we will be used in many threads, we need to protect
    // concurrent access
    inner: Mutex<u8>,
}

fn singleton() -> &'static SingletonReader {
    // Create an uninitialized static
    static mut SINGLETON: MaybeUninit<SingletonReader> = MaybeUninit::uninit();
    static ONCE: Once = Once::new();

    unsafe {
        ONCE.call_once(|| {
            // Make it
            let singleton = SingletonReader {
                inner: Mutex::new(0),
            };
            // Store it to the static var, i.e. initialize it
            SINGLETON.write(singleton);
        });

        // Now we give out a shared reference to the data, which is safe to use
        // concurrently.
        SINGLETON.assume_init_ref()
    }
}

fn main() {
    // Let's use the singleton in a few threads
    let threads: Vec<_> = (0..10)
        .map(|i| {
            thread::spawn(move || {
                thread::sleep(Duration::from_millis(i * 10));
                let s = singleton();
                let mut data = s.inner.lock().unwrap();
                *data = i as u8;
            })
        })
        .collect();

    // And let's check the singleton every so often
    for _ in 0u8..20 {
        thread::sleep(Duration::from_millis(5));

        let s = singleton();
        let data = s.inner.lock().unwrap();
        println!("It is: {}", *data);
    }

    for thread in threads.into_iter() {
        thread.join().unwrap();
    }
}

This prints out:

It is: 0
It is: 1
It is: 1
It is: 2
It is: 2
It is: 3
It is: 3
It is: 4
It is: 4
It is: 5
It is: 5
It is: 6
It is: 6
It is: 7
It is: 7
It is: 8
It is: 8
It is: 9
It is: 9
It is: 9

This code compiles with Rust 1.55.0.

All of this work is what lazy-static or once_cell do for you.

The meaning of "global"

Please note that you can still use normal Rust scoping and module-level privacy to control access to a static or lazy_static variable. This means that you can declare it in a module or even inside of a function and it won't be accessible outside of that module / function. This is good for controlling access:

use lazy_static::lazy_static; // 1.2.0

fn only_here() {
    lazy_static! {
        static ref NAME: String = String::from("hello, world!");
    }
    
    println!("{}", &*NAME);
}

fn not_here() {
    println!("{}", &*NAME);
}
error[E0425]: cannot find value `NAME` in this scope
  --> src/lib.rs:12:22
   |
12 |     println!("{}", &*NAME);
   |                      ^^^^ not found in this scope

However, the variable is still global in that there's one instance of it that exists across the entire program.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 110
    After a lot of thought I'm convinced not to use the Singleton, and instead use no global variables at all and pass everything around. Makes the code more self-documenting since it is clear what functions access the renderer. If I want to change back to singleton, it will be easier to do that than the other way around. – stevenkucera Jan 08 '15 at 00:48
  • 6
    Thanks for the answer, it helped a lot. I just thought I'd let here a comment to describe what I see as a valid use case for lazy_static!. I am using it to interface to a C application that allows loading/unloading modules (shared objects) and the rust code is one of these modules. I don't see much option than using a global on load because I have no control over main() at all and how the core application interfaces with my module. I basically needed a vector of things that can be added on runtime after my mod is loaded. – Moises Silva Aug 06 '16 at 17:11
  • 1
    @MoisesSilva there's always going to be *some* reason to need a singleton, but it's unnecessary to use it in many of the cases it is used. Without knowing your code, it's possible that the C application should allow each module to return a "user data" `void *` which is then passed back into each module's methods. This is a typical extension pattern for C code. If the application doesn't allow for this and you cannot change it, then yes, a singleton may be a good solution. – Shepmaster Aug 06 '16 at 17:15
  • 2
    Yeah passing around context would work, but this is a large application we don't really have much control over and changing the interface to modules would imply updating hundreds of third party modules or creating a new module API, both changes involve far more work than just writing a plugin module using lazy-static. – Moises Silva Aug 08 '16 at 14:56
  • 3
    Not a good answer. `Avoid global state in general` but global state is a thing and need representation. And the code for `external static` is flawed and will not compile on rustc 1.24.1 – Worik May 21 '18 at 21:47
  • 5
    @Worik would you care to explain why? I discourage people from doing something that is a poor idea in most languages (even the OP agreed that a global was a bad choice for their application). That's what **in general** means. I then show two solutions for how to do it anyway. I just tested the `lazy_static` example in Rust 1.24.1 and it works exactly. There's no `external static` anywhere here. Perhaps you need to check things on your end to make sure you've understood the answer fully. – Shepmaster May 21 '18 at 21:50
  • 1
    Why what? Global state applies to things like the environment a system runs in. This can mean a lot: Physical location, actual time of day are obvious examples that are common. But domain specific things like credentials or branding colours. In system programming it is very common to have significant set of data the is global to the system - the direction the car is moving in, the altitude of the aeroplane, the time since the fuse was lit.... As for the example you provided I could not make it work - I am new to Rust. You left out a edit to Cargo.toml IIRC – Worik May 22 '18 at 22:32
  • 1
    @Worik if you need help with the basics of how to use a crate, I suggest you re-read [*The Rust Programming Language*](https://doc.rust-lang.org/book/second-edition/). The [chapter on creating a guessing game](https://doc.rust-lang.org/book/second-edition/ch02-00-guessing-game-tutorial.html) shows how to add dependencies. – Shepmaster May 23 '18 at 00:01
  • Why not use static mut SINGLETON: *const SingletonReader = std::ptr::null() ??? – Justin Thomas May 10 '19 at 01:04
  • 1
    @JustinThomas *cribbed from [the Rust 1.0 implementation of `stdin`](https://github.com/rust-lang/rust/blob/2a8cb678e61e91c160d80794b5fdd723d0d4211c/src/libstd/io/stdio.rs#L217-L247)*. It appears [`ptr::null` only became `const` in 1.4.0](https://github.com/rust-lang/rust/commit/3903ea96f557dc923cf73d3905554083cd921a01). – Shepmaster May 10 '19 at 01:15
  • @Shepmaster Thanks! Are there any good Rust communities where noobs can ask questions? Instead of continuing to open terrible Stackoverflow questions :) – Justin Thomas May 10 '19 at 22:29
  • I liked your `Manual, dependency-free implementation`, how would you go about writing tests for code that uses this singleton instance? since each test is ran in it's own thread, and the state is shared it complicates testing right? – perrohunter Jun 19 '19 at 21:19
  • @Shepmaster Is it frowned upon to use global variables in a library? – Rokit May 31 '20 at 21:30
  • 2
    @Rokit there's always exceptions, but yes, I'd say that it's generally a bad idea to have global mutable variables in a library. Those prevent easily using the library in multiple different concurrent contexts. Immutable global variables are less bad (such as a compiled `Regex`). – Shepmaster Jun 01 '20 at 12:30
  • Wondering about the "non-answer answer"? How could a mutable reference to an early created object be thread-safe? – decades Oct 24 '20 at 18:48
  • @decades a mutable reference can be thread-safe, depending on the type of threads (see [How can I pass a reference to a stack variable to a thread?](https://stackoverflow.com/q/32750829/155423)). You can also create a `Mutex` and pass immutable references to it around. You can also create an `Arc>` and clone it and hand the clone to spawned threads. All of those can be passed around as normal variables. – Shepmaster Oct 26 '20 at 14:50
  • @Shepmaster Yes, thanks. I learned a lot during the past tow days :) Anyway, I still do not fully understand, what is the disadvantage of using with global state compared to passing mutable references to that state around. But don't bother, maybe I get wiser on my journey through Rust :) – decades Oct 27 '20 at 15:46
  • Is there anyway to tell rust, I want to use a `static mut`, but it'll only be used on the main thread and do no harm? `unsafe` in this case is fine but I wonder if there's a safe way to allow me use on main thread, and prevent me from using on another thread – tga Nov 20 '20 at 17:47
  • @tga you may want [`thread_local!`](https://doc.rust-lang.org/std/macro.thread_local.html) instead. There is no syntax to indicate that a `static mut` is scoped to a specific thread. The Rust _language_ has no concept of threads, so such syntax wouldn't make sense to me. The knowledge that you are only ever going to use something on a single thread is outside what can be statically verified. I'd _highly_ recommend just creating the value in `main` and passing it around. – Shepmaster Nov 20 '20 at 17:57
  • The manual singleton is not very thread safe because when people adapt it to their own use cases, there is no check that it be `Send`. The library solutions do impose that requirement. – piojo May 13 '21 at 03:28
  • The _manual dependency-free implementation_ would work nicely without introducing `Arc`. Since the singleton is global/static, `singleton()` can return `&'static SingletonReader`, which is `Copy` and thread-safe. Additionally, the raw pointer, the allocation and the transmute can be avoided using `MaybeUninit`. The modified example would [look like this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=cf28d288e5e52520102fff262697749f). If you agree with the change, I can edit the answer. – user4815162342 Nov 01 '21 at 21:34
  • I've now applied the edit mentioned in the previous comment, simplifying the code somewhat and adapting the surrounding prose accordingly. I've also removed the paragraph suggesting to auto-lock by implementing `Deref` and `DerefMut`, because I think it actually doesn't work - both types require a _reference_, and for locking they'd need to return a lock guard. (That kind of thing would be possible with GATs, if `Deref` were to use them.) If that is incorrect, please re-add the paragraph, or let me know and I'll re-add it. – user4815162342 Nov 06 '21 at 19:54
  • Note that "use a main" does some _heavy_ lifting, because that only works for code that is going to be compiled as an executable. Library code doens't use a main, and will error out if you try to build it with a main function. And even for a toy library like a Fibonacci calculator, you need a global lookup table so that you're not constantly recomputing numbers that were already computed previously. It would be nice if this answer addressed that use-case, especially since the intuitive (to a novice) `let mut somename = ...` will _not_ work in global scope. – Mike 'Pomax' Kamermans Aug 06 '23 at 18:41
26

Starting with Rust 1.63, it can be easier to work with global mutable singletons, although it's still preferable to avoid global variables in most cases.

Now that Mutex::new is const, you can use global static Mutex locks without needing lazy initialization:

use std::sync::Mutex;

static GLOBAL_DATA: Mutex<Vec<i32>> = Mutex::new(Vec::new());

fn main() {
    GLOBAL_DATA.lock().unwrap().push(42);
    println!("{:?}", GLOBAL_DATA.lock().unwrap());
}

Note that this also depends on the fact that Vec::new is const. If you need to use non-const functions to set up your singleton, you could wrap your data in an Option, and initially set it to None. This lets you use data structures like Hashset which currently cannot be used in a const context:

use std::sync::Mutex;
use std::collections::HashSet;

static GLOBAL_DATA: Mutex<Option<HashSet<i32>>> = Mutex::new(None);

fn main() {
    *GLOBAL_DATA.lock().unwrap() = Some(HashSet::from([42]));
    println!("V2: {:?}", GLOBAL_DATA.lock().unwrap());
}

Alternatively, you could use an RwLock, instead of a Mutex, since RwLock::new is also const as of Rust 1.63. This would make it possible to read the data from multiple threads simultaneously.

If you need to initialize with non-const functions and you'd prefer not to use an Option, you could use a crate like once_cell or lazy-static for lazy initialization as explained in Shepmaster's answer.

Daniel Giger
  • 2,023
  • 21
  • 20
6

From What Not To Do In Rust

To recap: instead of using interior mutability where an object changes its internal state, consider using a pattern where you promote new state to be current and current consumers of the old state will continue to hold on to it by putting an Arc into an RwLock.

use std::sync::{Arc, RwLock};

#[derive(Default)]
struct Config {
    pub debug_mode: bool,
}

impl Config {
    pub fn current() -> Arc<Config> {
        CURRENT_CONFIG.with(|c| c.read().unwrap().clone())
    }
    pub fn make_current(self) {
        CURRENT_CONFIG.with(|c| *c.write().unwrap() = Arc::new(self))
    }
}

thread_local! {
    static CURRENT_CONFIG: RwLock<Arc<Config>> = RwLock::new(Default::default());
}

fn main() {
    Config { debug_mode: true }.make_current();
    if Config::current().debug_mode {
        // do something
    }
}
Paul Houghton
  • 149
  • 2
  • 3
  • 6
    Hi, please see [this question](https://stackoverflow.com/questions/68807035/understanding-a-thread-safe-rwlockarct-mechanism-in-rust) as I'm not sure the `thread_local` is correct, as it will create multiple instances of the `Arc` (one per running thread). – thargy Aug 16 '21 at 17:37
3

If you are on nightly, you can use LazyLock.

It more or less does what the crates once_cell and lazy_sync do. Those two crates are very common, so there's a good chance they might already by in your Cargo.lock dependency tree. But if you prefer to be a bit more "adventurous" and go with LazyLock, be prepered that it (as everything in nightly) might be a subject to change before it gets to stable.

(Note: Up until recently std::sync::LazyLock used to be named std::lazy::SyncLazy but was recently renamed.)

at54321
  • 8,726
  • 26
  • 46
2

Use SpinLock for global access.

#[derive(Default)]
struct ThreadRegistry {
    pub enabled_for_new_threads: bool,
    threads: Option<HashMap<u32, *const Tls>>,
}

impl ThreadRegistry {
    fn threads(&mut self) -> &mut HashMap<u32, *const Tls> {
        self.threads.get_or_insert_with(HashMap::new)
    }
}

static THREAD_REGISTRY: SpinLock<ThreadRegistry> = SpinLock::new(Default::default());

fn func_1() {
    let thread_registry = THREAD_REGISTRY.lock();  // Immutable access
    if thread_registry.enabled_for_new_threads {
    }
}

fn func_2() {
    let mut thread_registry = THREAD_REGISTRY.lock();  // Mutable access
    thread_registry.threads().insert(
        // ...
    );
}

If you want mutable state(NOT Singleton), see What Not to Do in Rust for more descriptions.

Hope it's helpful.

1

A bit late to the party, but here's how I worked around this issue (rust 1.66-nightly):

#![feature(const_size_of_val)]
#![feature(const_ptr_write)]

static mut GLOBAL_LAZY_MUT: StructThatIsNotSyncNorSend = unsafe {
    // Copied from MaybeUninit::zeroed() with minor modifications, see below
    let mut u = MaybeUninit::uninit();

    let bytes = mem::size_of_val(&u);
    write_bytes(u.as_ptr() as *const u8 as *mut u8, 0xA5, bytes); //Trick the compiler check that verifies pointers and references are not null.

    u.assume_init()
};

(...)

fn main() {
    unsafe {
        let mut v = StructThatIsNotSyncNorSend::new();
        mem::swap(&mut GLOBAL_LAZY_MUT, &mut v);
        mem::forget(v);
    }
  
}

Beware that this code is unbelievably unsafe, and can easily end up being UB if not handled correctly.

You now have a !Send !Sync value as a global static, without the protection of a Mutex. If you access it from multiple threads, even if just for reading, it's UB. If you don't initialize it the way shown, it's UB, because it calls Drop on an actually unitialized value.

You just convinced the rust compiler that something that is UB is not UB. You just convinced that putting a !Sync and !Send in a global static is fine.

If unsure, don't use this snippet.

Ákos Vandra-Meyer
  • 1,890
  • 1
  • 23
  • 40
  • Why is this not UB? You are creating an uninitialized value, which, AFAIK, is instantaneous UB even if you don't read it before properly initializing it. Also, you have an `unsafe` block within an `unsafe` block (maybe to point out it *really* unsafe?) – jthulhu Feb 03 '23 at 09:37
  • The nested unsafe is not necessary, that is a mistake. The code is not UB because the value is overwritten before it is read (the initialization needs to be in main, before any race conditions could happen). What you may be referring to (UB even it it's not read) happens if the value is overwritten, and the rust compiler tries to Drop the old (unitited) value. Since it's `mem::swap`-ped and `mem::forget`-ted here, that doesn't happen. – Ákos Vandra-Meyer Feb 03 '23 at 19:47
  • That may seem intuitively correct, and yet, even having an unitialized variable, *even if you never access it*, is UB, I think. See [the doc](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html), in particular the example with `bool` which seems to match your snippet. – jthulhu Feb 03 '23 at 22:10
  • I feel like you're not correct, but I'm not entirely sure either. These two snippets in the doc seem to contradict: `For example, a 1-initialized Vec is considered initialized (under the current implementation; this does not constitute a stable guarantee) because the only requirement the compiler knows about it is that the data pointer must be non-null. Creating such a Vec does not cause immediate undefined behavior, but will cause undefined behavior with most safe operations (including dropping it).`, meaning that initializing the memory to 0xA5 will not cause immediate UB, however – Ákos Vandra-Meyer Feb 03 '23 at 22:18
  • It also states that `Similarly, entirely uninitialized memory may have any content, while a bool must always be true or false. Hence, creating an uninitialized bool is undefined behavior:`, which kinda supports what you said, but it is totally uninited in this case – Ákos Vandra-Meyer Feb 03 '23 at 22:18
  • In the doc, it says what kind of initialization causes UB depends on the type in question. For instance, for a pointer, the requirements are just to be non-null and aligned, whereas for an enum, the discriminant must be valid (for instance, for a bool, there are only two valid values). For an arbitrary type, I don't know what exactly is considered UB, but I suspect simply writing `0xA5` does not preclude UB. I'm not an expert, though, I think it would be wise to ask on a Rust forum. – jthulhu Feb 03 '23 at 22:23
  • 1
    https://users.rust-lang.org/t/is-this-ub-or-not/88739 – Ákos Vandra-Meyer Feb 03 '23 at 22:24
0

Besides the 3rd-party crates, the alternative is to wrap your custom type (e.g. struct) in std::cell::Cell within std::sync::Mutex.

  • The Mutex protects the instance of the custom type in concurrent accesses of multi-threading usage scenario
  • The Cell provides interior mutability so the one getting the lock from Mutex can modify the content of the custom type.

Here's the code example :

use std::cell::Cell;
use std::sync::Mutex;

#[derive(Debug)]
struct Rectangle {
    width :u16,
    height:u16,
}

static GLOBAL_COUNTER_2: Mutex<Cell<Rectangle>> = 
    Mutex::new(Cell::new(
        Rectangle{width:100u16, height:125u16}
    ));

fn  global_var_demo()
{
    if let Ok(mut currcell) = GLOBAL_COUNTER_2.lock() {
        let mut value = currcell.get_mut();
        value.width += 7;
        value.height = value.height >> 1;
    }
    if let Ok(mut currcell) = GLOBAL_COUNTER_2.lock() {
        // request the reference without moving the ownership
        let value = currcell.get_mut();
        assert_eq!(value.width, 107u16);
        println!("new value in GLOBAL_COUNTER_2: {:?}", value);
    }
}
Ham
  • 703
  • 8
  • 17
-2

My limited solution is to define a struct instead of a global mutable one. To use that struct, external code needs to call init() but we disallow calling init() more than once by using an AtomicBoolean (for multithreading usage).

static INITIATED: AtomicBool = AtomicBool::new(false);

struct Singleton {
  ...
}

impl Singleton {
  pub fn init() -> Self {
    if INITIATED.load(Ordering::Relaxed) {
      panic!("Cannot initiate more than once")
    } else {
      INITIATED.store(true, Ordering::Relaxed);

      Singleton {
        ...
      }
    }
  }
}

fn main() {
  let singleton = Singleton::init();
  
  // panic here
  // let another_one = Singleton::init();
  ...
}
Thach Van
  • 1,381
  • 16
  • 19