Search code examples
staticrustmutability

Parsing data into a module-level mutable static variable


I have a set of functions within a module that need access to some shared initialization-time state. Effectively I'd like to model this with a static mutable vector like:

static mut defs: Vec<String> = vec![];

fn initialize() {
    defs.push("One".to_string());
    defs.push("Two".to_string()); 
}

(Example: http://is.gd/TyNQVv, fails with "mutable statics are not allowed to have destructors".)

My question is similar to Is it possible to use global variables in Rust?, but uses a Vec (i.e. a type with destructor), so the Option-based solution to that question doesn't seem to apply. Namely, this fails with the same error as my first attempt:

static mut defs: Option<Vec<String>> = None;

fn initialize() {
    let init_defs = vec![];
    init_defs.push("One".to_string());
    init_defs.push("Two".to_string()); 
    defs = Some(init_defs);
}
  1. Is there a way to get access to a static ("global") vector that is populated at initialization time and visible at runtime?

  2. Are there other patterns I should be considering to support this use case? Passing explicit references to the state vector is possible, but would clutter up a very large number of function signatures that all need access to this state.


Solution

  • You can use lazy_static for this purpose:

    lazy_static! {
        static ref defs: Vec<String> = {
            let mut init = vec!["One".to_string(), "Two".to_string()];
            // init.push(...); etc. etc.
            init
        }
    }
    

    That initialises a vector on the first access, and it is immutable after that. If you wish to modify it later, wrapping it in a std::sync::Mutex is a good first step.

    Are there other patterns I should be considering to support this use case? Passing explicit references to the state vector is possible, but would clutter up a very large number of function signatures that all need access to this state.

    One pattern to consider is creating a context object that stores all the info the functions need, e.g.

    struct Context {
        defs: Vec<String>
    }
    

    and then passing around Context ensures everyone knows what they need to know. You can even consider putting all/many/some of the functions as methods on Context, e.g.

    impl Context {
        fn foo(&self) {
            if self.defs.len() > 10 {
                 println!("lots of defs");
            }
        }
        // ...
    }
    

    This pattern is especially good if you need to modify the context (automatically ensures thread safety), and/or if you wish to have several independent instances in a single process.