Application environment (state/configuration) global variables

I require advice on a suitable approach for handling of application variables (generally objects via reference) that are global in nature, that are needed by various components of the application and the libraries used.

An example of such an application wide global variable, would be a locale object for user's current selected locale, so that information displayed to the user is formatted correctly (of course the locale translated strings are present) for the specified locale, else fallback to the default locale of application, or library. Other possible global variables would be theme, network ports, database connections, even a global logger, etc.

Due to how Rust handles data with safety mechanism of ownership and borrowing, it seems to make the concept of mutable global objects (part of a single global structure) not idea, or seemingly complex to implement. Another idea is to pass the references to "global" objects created in main() to the components (in a chain fashion), though the objects closer to main() could have a large collection of object references in order to pass the global objects to their children components.

I am fairly new to Rust, still getting the hang of the programming paradigms used for Rust compared to other OOP languages.

I would say this is the idiomatic way to go about it. I haven't found any use for global variables yet.

1 Like

So for some context, Rust assumes you are going (perhaps eventually) be using things in a threaded manner. So if you had some usage like:

pub static mut LOCALE: String = String::new();

fn main() {
  unsafe { LOCALE = String::new("tlh-Latn") };
}

fn get_localized() {
  let loc: &str = unsafe { &LOCALE };
  ...
}

The unsafe is because Rust doesn't know that only main will assign to LOCALE, and thus that reads and writes won't race.

So the nasty answer is to just factor the above unsafe out into it's own module that can document the valid usage:

mod globals {
  static mut LOCALE: String = String::new();
  // ...

  // Safety:
  // * must only be called before any threads that will access this module are spawned,
  //   for example at the top of `main()`
  pub unsafe fn initialize() {
    unsafe { LOCALE = String::new("tlh-Latn") };
    // ...
  }

  pub fn get_locale(): &str {
    // Safety:
    // * only mutation is through initialize(), which must be called before any race is possible.
    unsafe { &LOCALE }
  }

  // ...
}

The nicer answer if your globals can be duplicated per-thread:

std::thread_local! {
  // can't be mut, but can be initialized (expression runs on first access for each thread)
  pub static LOCALE: String = String::new("tlh-Latn");
  // ...
}

fn get_localized() {
  LOCALE.with(|locale| {
    // ...
  };
}

and then there's crates like the popular lazy_static that are basically the same thing but not thread-local (the tradeoff being that it is possible much more expensive to initialize and access)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.