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!

7
  • 1
    Does this answer your question? How do I create a global, mutable singleton? Commented Oct 15, 2020 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. Commented Oct 15, 2020 at 11:22
  • How about this one? stackoverflow.com/questions/27221504/… Commented Oct 15, 2020 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 (this applies to both lazy_static and once_cell). Commented Oct 15, 2020 at 11:38
  • 2
    Here's a safe translation of the C++ code using lazy_static. 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?. Commented Oct 15, 2020 at 14:59

2 Answers 2

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

Sign up to request clarification or add additional context in comments.

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.