英文:
&str with lifetime for "After Deserialization"
问题
我正在处理生命周期和&str
的问题。
在我的程序中,有一堆字符串,它们实际上不是静态的,但非常接近:它们在初始化时从 JSON 中加载,并应在整个程序的生命周期内保持在作用域内。
我有一堆结构体在使用这些字符串。我希望在这些结构体中只使用&str
,以便支持Copy
等操作。我可以通过在结构体中引入很多生命周期参数来实现这一点,但这些参数会传播到各处,而且每次我使用它们时都会出错。另一方面,我也不想在代码中到处充斥着.to_owned()
和.clone()
,因为这些字符串总是相同的,而且它们从不被改变。
我几乎可以说这些是&'static str
字符串,但我认为这不太准确,我还没有找到一种方法来实现。然而,它们将在程序启动时从“初始化”函数中出来,并在程序结束后仍然保持在作用域内。
这反映了我一直以来的一个问题 - 想要引用某些意义上的“长寿命”对象的引用(比关联类型用于的寿命更长,或比某个函数的所有调用的寿命都要长)。
我可以想到的一种方法是在几乎所有东西上都使用一个'program
寿命,并尝试始终维护该参数始终引用(post-initialization)程序的实际寿命。尽管如此,这听起来会产生大量样板代码,并可能在后续出现问题。
真正理想的情况是在模块级别上具有生命周期参数 - 基本上表示对于给定模块中的所有内容,某些东西保持在作用域内。我不认为有办法做到这一点,对吗?
有更好的方法吗?或者有办法将从某个任意函数返回的字符串转换为有效的'static
引用吗?
英文:
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?
答案1
得分: 0
你的用例似乎非常适合使用 lazy_static
或 once_cell
crate,或者标准库的 api(稍后会详细介绍)。
lazy cell 的基本用法是:
#![feature(lazy_cell)]
use std::sync::LazyLock;
use rand;
static LAZY: LazyLock<String> = LazyLock::new(|| {
rand::random::<f64>().to_string() // <-- 假装这里加载了 json
});
fn main() {
println!("{}", *LAZY);
let lazy_ref: &'static str = &*LAZY;
println!("{}", lazy_ref);
}
你会注意到代码顶部有一个 feature
属性。这只在夜间版本的 Rust 中工作,但它基本上是基于一个现有的 crate,该 crate 在稳定版中也可用,叫做 once_cell(将 use
替换为 once_cell::sync::Lazy
)。lazy_static
的工作方式类似,但使用了一个宏,并且不会包含在标准库中,因此我选择展示如何使用 LazyLock
。
你想一次性读取和解析所有的 json,因为为每个设置重新解析 json 会非常慢。我认为你最好的选择是将 json 文件解析为一个包含每个字符串的字段的结构体。
#![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; // 依然是 'static
println!("{}", string1);
// Strings.string2 已被惰性加载
}
英文:
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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论