How to store file contents for global access

I have a JSON file containing some data I need in several functions of my application. Currently I just read the file from my hard drive every time I need it.

use serde_json::Value;

pub fn data_from_file() -> Vec<Value> {
    serde_json::from_str::<Value>(
        &std::fs::read_to_string("data.json").unwrap()
    ).unwrap().as_array().unwrap().clone()
}

pub fn a_function() {
    let data = data_from_file();
    // do something with the data
}

However, I suppose this is kind of inefficient. The file content does not change while the program is running. So it should be sufficient to read the file once at the start of the program and store it somewhere for future use. But I'm not sure what or where this "somewhere" could be. I thought about global variables, but using them in Rust seems to be quite complicated and I am not convinced if this is really the best way to go. So, are there any best practices how to solve this kind of problem?

Thank you in advance for any answers. :slightly_smiling_face:

If the data is fixed at build time you can use include_str. Here's how that might look combined with lazy_static to handle the JSON parsing:

use lazy_static::lazy_static;
use serde_json::Value;

lazy_static! {
    // if `test.json` is in the crate root, next to `Cargo.toml`
    static ref VALUES: Vec<Value> = serde_json::from_str(include_str!("../test.json")).unwrap();
}

fn main() {
    dbg!(&*VALUES);
}
2 Likes

Thank you. This is exactly what I was looking for.

(It's also nice to see that the .unwrap().as_array().unwrap().clone() in my code wasn't really necessary. It looked a bit strange to me, but I hadn't known how to do it better; now I know.)

1 Like

You can deserialize into any type that impls Deserialize. There's usually no need to go through an intermediate, in-memory, weakly-typed representation like serde_json::Value. The whole point of Serde is to deserialize into any strongly-typed Rust value.

(Occasionally, the dynamically-typed representation is useful, but that is the exception rather than the rule. Deserializing directly into your domain types is both simpler and faster.)

And while we are at simplifying things: consider using once_cell::Lazy instead of lazy_static! – it provides the same functionality, but without macros.

2 Likes

Is it in general better to avoid macros, whenever possible? If so, why?

No. Macros are not bad. You don't have to "avoid them whenever possible".

But why use a macro where a simple expression would do?

Macros are a bit more heavy-handed solution to problems where you need to abstract over syntax, i.e. when you can't do something purely by the means of the type system or other built-in capabilities of the language. They are useful for extending the language when it can't fully solve your exact problem.

So, if something can be done without macros, why bother with macros? Just use the built-in functionality – that will be clear to everyone reading the code. (To be fair, very well-known macros such as lazy_static! won't confuse anyone. But if you are writing your own, it's easy to get excessively clever and terse using macros.)

Thanks for your reply. I think I understand what you mean. For now, I'll nevertheless stick with lazy_static!, because for some reason I find it a bit easier to read. But I'll keep your suggestion in mind for future use. :slightly_smiling_face: