I'm beating my head on lifetimes and &str
.
I have a bunch of strings in my program that aren't actually static, but come close: they're loaded from a json at initialization, and should remain in scope for the entire length of the program.
I have a bunch of structs that use these. I would like to get away with just having &strs
in those structs, for Copy
, etc. I can do this by having lots of lifetime parameters, but those propagate everywhere and every time I've used them they've been a mistake. On the other hand, I don't want to flood my code with .to_owned()
s and .clone()
s either, when it's always the same strings and they're never mutated.
I can almost say these are & 'static str
stings, but I don't think that's quite true and I haven't found a way to do so. However, they'd come out of "initialization" functions at the start of the program, and remain in scope past the end.
This mirrors a problem I've had in general - wanting references that refer to something whose lifetime is "long" in some sense (longer than an associated type is used for, or longer than all of the invocations of some function).
One way I can see to do this is to have a 'program
lifetime that goes on basically everything, and try to maintain that that parameter always refers to the actual lifetime of the (post-initialization) program. That sounds like it's going to be a lot of boiler plate and get me int trouble down the line, though.
What would be truly ideal would be to have lifetime parameters on the module level - basically say that for everything from a given module, certain things stay in scope. I don't think there's any way to do that, is there?
Is there a better way to do this? Or a way to take a String that comes out of some arbitrary function and get a valid 'static
reference to it?
Your usecase seems like a perfect fit for the lazy_static
crate, or the once_cell
crate/standard library api (more on this later).
The basic use of lazy cell, is
#![feature(lazy_cell)]
use std::sync::LazyLock;
use rand;
static LAZY: LazyLock<String> = LazyLock::new(|| {
rand::random::<f64>().to_string() // <-- pretend this loads json
});
fn main() {
println!("{}", *LAZY);
let lazy_ref: &'static str = &*LAZY;
println!("{}", lazy_ref);
}
You'll notice the feature
attribute at the top of the code. This only works in nightly rust, but it is heavily based on an existing crate that works in stable, called once_cell (replace the use
with once_cell::sync::Lazy
). lazy_static
works similarly, but uses a macro and won't be included in the standard library, so I chose to show how to use LazyLock
instead.
You want to read and parse all your json at once, as re-parsing the json for every setting would be very slow. I think your best option is to parse your json file into a struct with a field for every string.
#![feature(lazy_cell)]
use std::sync::LazyLock;
use std::fs::File;
use serde_derive::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Strings {
string1: String,
string2: String,
}
static STRINGS: LazyLock<Strings> = LazyLock::new(|| {
serde_json::from_reader(File::open("strings.json").unwrap()).unwrap()
});
fn main() {
let string1: &'static str = &STRINGS.string1; //yep still 'static
println!("{}", string1);
//Strings.string2 has been lazily loaded
}