Translating Cpp to Rust, handling a global static object

488 views Asked by At

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!

2

There are 2 answers

0
Lucy On

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

0
Masklinn On

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