2

I'm a beginner translating a familiar Cpp project to Rust. The project contains a class called Globals which stores global config parameters. Here is an extract from its cpp file:

static Globals &__get()
{
  static Globals globals;
  return globals;
}

const Globals &Globals::get()
{
  auto &globals = __get();
  if (!globals.initialized) {
    throw std::runtime_error("Initialize globals first");
  }
  return globals;
}

void Globals::set(const Globals &globals)
{
  __get() = globals;
}

How would I translate this to Rust? From what I can tell __get() implements some kind of singleton logic. I've read about the lazy_static crate to achieve something similar, but unlocking the variable each time I want to read its value seems to be too verbose. Isn't it possible to achieve this using an interface like Globals::get() in the cpp code.

I rarely post, so if I forgot something, just tell me and I'll provide details. Thanks!

  • 1
    Does this answer your question? [How do I create a global, mutable singleton?](https://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton) – E_net4 Oct 15 '20 at 11:06
  • Kind of, the big difference is that my global struct is immutable, and thus should be safe to read and then also shouldn't require a Mutex (I guess?). However, lazy_static requires the Sync trait to be implemented, and I see why, but in my case it doesn't make sense. – Moostropfen Oct 15 '20 at 11:22
  • How about this one? https://stackoverflow.com/questions/27221504/how-can-you-make-a-safe-static-singleton-in-rust – E_net4 Oct 15 '20 at 11:24
  • 3
    @Moostropfen the existence of `set` makes your global mutable. It may only be *temporarily* mutable e.g. you set it once at the start of the program, but Rust's got no way to know this. Also while the first link covers mutable globals, it also covers (somewhat implicitly) the ability to set-once then just access: [just don't use the mutex](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5e390c7e5ee6985034618bfcd0379c3a) (this applies to both lazy_static and once_cell). – Masklinn Oct 15 '20 at 11:38
  • 2
    [Here's a safe translation of the C++ code using `lazy_static`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9ca1ce6cecd39344b53b5e993cd67e22). I maybe overcomplicated it a bit by using an `Option` instead of having a flag inside `Globals` that indicates initialized-ness, because that means you can't return an `RwLockReadGuard` from `get()` directly, so I had to wrap it myself using some of the ideas in [How do I return a reference to something inside a RefCell without breaking encapsulation?](https://stackoverflow.com/q/29401626/3650362). – trent Oct 15 '20 at 14:59
  • Two other differences: (1) the Rust versions of `set` and `get` will block the current thread until `GLOBALS` is no longer borrowed / borrowed mutably, which is how it ensures safety; and (2) the C++ `get` throws `"Initialize globals first"` when called, but the Rust `get` just returns a guard which will panic when dereferenced (which is not much of a difference if you're going to use it anyway). [Here's a slightly different translation that's more true to the C++ semantics](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5efa0fa8f14c12db545467e025df75e8). – trent Oct 15 '20 at 15:08
  • Thank you all, very good answers. I found that there is some global entropy, that changes for each iteration in the main loop, so it has to be mutable! – Moostropfen Oct 30 '20 at 14:13

2 Answers2

3

I've read about the lazy_static crate to achieve something similar, but unlocking the variable each time I want to read its value seems to be too verbose.

For a good reason: "safe rust" includes what you'd call thread-safety by design. An unprotected mutable global is wildly unsafe.

Which... is why interacting with a mutable static requires unsafe (whether reading or writing).

The translation from C++ is quite straightforward[0] and can easily be inferred from the reference section on statics, the only real divergence is that a Rust static must be initialised.

Also note that if you do not need mutation then you can lazy_static! a readonly value (you don't need the mutex at all), and don't need to unlock anything.

[0] though much simplified by doing away with the unnecessary __get

Masklinn
  • 34,759
  • 3
  • 38
  • 57
1

Rust requires memory safety in safe code - thus, you cannot have a mutable static in safe code. You CAN have an atomic static (see AtomicBool or AtomicU64 for examples), but for a normal type, you will need some sort of locking mechanism, such as an RwLock or Mutex (if performance is your thing, the parking_lot crate provides more performant implementations than the Rust standard library)

If you don't want to handle locking yourself, may I suggest making a wrapper object using getter/setter methods?

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

static GLOBAL: Lazy<Global> = Lazy::new(Global::new);

struct GlobalThingymajig {
    pub number: u32,
    pub words: String,
}

pub struct Global(Arc<RwLock<GlobalThingymajig>>);

impl Global {
    pub fn new() -> Self {
        Self(Arc::new(RwLock::new(
            GlobalThingymajig {
                number: 42,
                words: "The Answer to Life, The Universe, and Everything".into()
            }
        )))
    }

    pub fn number(&self) -> u32 {
        self.0.read().unwrap().number
    }

    pub fn words(&self) -> String {
        self.0.read().unwrap().words.clone()
    }

    pub fn set_number(&self, new_number: u32) {
        let mut writer = self.0.write().unwrap();
        writer.number = new_number;
    }

    pub fn set_words(&self, new_words: String) {
        let mut writer = self.0.write().unwrap();
        writer.words = new_words;
    }
}

You can see this example on the Rust Playground here

Lucy
  • 31
  • 1
  • 5