Idiomatic (oxidised?) way to store command line arguments for later use?

I'm learning Rust, and writing a command line program. I'm parsing the command line arguments into a struct with the structopt crate, and now I want to store that struct somewhere so I can read its members from other places in the program. In any other language that somewhere would be a global variable, but those are tricky in Rust.

So what is the idiomatic way to store a write-once read-many object like this? I could use a static mut, but then I'm immediately resigning to unsafe code. I don't want to pass a reference to the struct everywhere throughout the program either. There are also a number of ways using macros such as thread_local. But I'm not sure which of these would be idiomatic in a case like mine. I'm sure this use case has come up many times before.

You could create a container struct which stores the options as a field then implement your program's logic under that struct. Or you could always take the options as a parameter in the functions you need it in. There's other ways to handle it as well, but these tend to be the most straightforward in my opinion.

pub struct App {
    options: Opt,
}

impl App {
    pub fn new(options: Opt) -> Self {
        Self { options }
    }

    pub fn run(&mut self) {
        // main logic here, self.options is accessible
    }
}

fn main() {
    let options = (/* load your options and pass to app */);
    App::new(options).run()
}
1 Like

I'd say the idiomatic way is to pass options explicitly to the parts of code that need them. To avoid passing references, you can wrap the options object into an Arc. However, ideally not all parts of the code should receive the full options object. Instead, each part should only receive the data it cares about.

1 Like

I use

lazy_static!{
    pub static ref CONFIG: Config = Config::new().expect("Error in config");
}
pub struct Config {
    pub foo,
    ...
}

I can use CONFIG.foo to access the foo member of the Config struct anywhere that has a use crate::config::CONFIG.

Sounds like you might be interested in a std::lazy::OnceCell - Rust? (Or maybe once_cell — Rust library // Lib.rs while the std one is unstable.)

2 Likes

Honestly I'd rather pass it everywhere throughout the program manually, ideally only pass the smallest subset of it required by each function, to make it possible to unit test them independently. Unit tests should be run against various inputs and it's really hard to run them correctly if they all view the same memory address.

5 Likes

I really like the Lazy from that library. Then it's just

use once_cell::sync::Lazy;
static OPTS: Lazy<Opt> = Lazy::new(Opt::from_args);

and later you can use it directly as OPTS.some_field without any extraneous get(), unwrap() and the like. In addition the fast path doesn't use any expensive atomic instructions, just a (free on x86) memory barrier. Too bad this Lazy isn't scheduled for inclusion in std.

It's there too: std::lazy::Lazy - Rust

(Just not stable yet.)

hmm, don't you mean std::lazy::SyncLazy?

2 Likes

great! I didn't look at the std version a whole lot apparently.