修复使用 lazy_static 时的 serde 生命周期问题
Fix serde lifetime issue when using lazy_static
我想将一些 json 读入静态 HashMap
,并且正在使用 lazy_static
和 serde
,但我不知道如何(如果有的话)我可以解决这个 serde
生命周期问题:
#[macro_use]
extern crate lazy_static;
use std::fs::File;
use std::io::BufReader;
use std::collections::HashMap;
lazy_static! {
static ref KEYWORDS: HashMap<&'static str, i32> = {
let file = File::open("words.json").unwrap();
let reader = BufReader::new(file);
serde_json::from_reader(reader).unwrap()
};
}
error: implementation of serde::de::Deserialize is not general enough
note: HashMap<&str, i32> must implement serde::de::Deserialize<'0>, for any lifetime '0
note: but HashMap<&str, i32> actually implements serde::de::Deserialize<'1>, for some specific lifetime '1
words.json
是一个简单的 json 映射:{"aaargh": 1}
.
如果需要,我愿意接受另一种非lazy_static
方法。
错误消息告诉您不能反序列化为 &'static str
。随着反序列化器创建条目,&str
键的生命周期只能与反序列化器正在将文件读入的缓冲区借用一样长。但是 &'static str
必须指向一个永远存在的 str
。
我在这里看到两个解决方案:简单的方法和困难的方法。
最简单的方法:只需将类型中的 &'static str
更改为 String
即可编译。这样 HashMap
拥有密钥; serde 已经知道如何反序列化拥有的字符串。
static ref KEYWORDS: HashMap<String, i32> = { // ...
困难的方法:从技术上讲,您仍然可以通过泄漏 String
的后备缓冲区来获得 HashMap<&'static str, i32>
。通常 "leaking" 是不好的,但因为这是一个惰性静态,所以它真的没有什么区别,因为那些缓冲区永远不会被释放。通过泄漏 String
获得 &'static str
看起来像这样:
fn leak_string(from: String) -> &'static str {
Box::leak(from.into_boxed_str())
}
问题是 serde 不会自动执行此操作。实现此目的的一种方法是先反序列化为 HashMap<String, i32>
,然后通过获取每个条目并将它们插入新的 HashMap
之后将其转换为 HashMap<&'static string, i32>
运行通过 leak_string
的键。这是低效的,因为首先不需要收集到 HashMap
中。更好的解决方案是编写一个执行 leak_string
"on the fly" 的自定义反序列化器。由于简单的方法简单得多,而且这种困难的方法有一些绊脚石,我认为在这里提供完整的代码示例没有用。
"the hard way" 与 "the easy way" 的唯一真正优势是 "the hard way" 需要一个指针的价值更少的内存用于 HashMap
中的每个键(&str
是指针+len;String
是指针+len+容量)。它也很好,因为它不会改变你的类型签名,但是你可以用 &'static str
做的事情很少,而你不能用 String
.
这里的罪魁祸首是 serde_json::from_reader
的定义方式。来自 its documentation:
pub fn from_reader<R, T>(rdr: R) -> Result<T> where
R: Read,
T: DeserializeOwned,
所以,结果一定是自己的数据,不是借来的。即使 &'static
也不行。你必须在这里使用String
:
lazy_static! {
static ref KEYWORDS: HashMap<String, i32> = {
let file = File::open("words.json").unwrap();
let reader = BufReader::new(file);
serde_json::from_reader(reader).unwrap()
};
}
当使用 serde_json::from_str
从 &str
⟶ HashMap<&str, i32>
反序列化时,输入的 JSON 字符串需要比输出中的字符串切片长。这是'a
生命周期在签名中的作用:https://docs.rs/serde_json/1.0.40/serde_json/fn.from_str.html
这意味着如果输出需要包含具有 'static
生命周期的字符串切片,则输入 JSON 数据也必须具有 'static
生命周期。我们知道该怎么做 -- lazy_static!
use lazy_static::lazy_static;
use std::collections::HashMap;
lazy_static! {
static ref KEYWORDS: HashMap<&'static str, i32> = {
lazy_static! {
static ref WORDS_JSON: String = {
std::fs::read_to_string("words.json").unwrap()
};
}
serde_json::from_str(&WORDS_JSON).unwrap()
};
}
我想将一些 json 读入静态 HashMap
,并且正在使用 lazy_static
和 serde
,但我不知道如何(如果有的话)我可以解决这个 serde
生命周期问题:
#[macro_use]
extern crate lazy_static;
use std::fs::File;
use std::io::BufReader;
use std::collections::HashMap;
lazy_static! {
static ref KEYWORDS: HashMap<&'static str, i32> = {
let file = File::open("words.json").unwrap();
let reader = BufReader::new(file);
serde_json::from_reader(reader).unwrap()
};
}
error: implementation of serde::de::Deserialize is not general enough
note: HashMap<&str, i32> must implement serde::de::Deserialize<'0>, for any lifetime '0
note: but HashMap<&str, i32> actually implements serde::de::Deserialize<'1>, for some specific lifetime '1
words.json
是一个简单的 json 映射:{"aaargh": 1}
.
如果需要,我愿意接受另一种非lazy_static
方法。
错误消息告诉您不能反序列化为 &'static str
。随着反序列化器创建条目,&str
键的生命周期只能与反序列化器正在将文件读入的缓冲区借用一样长。但是 &'static str
必须指向一个永远存在的 str
。
我在这里看到两个解决方案:简单的方法和困难的方法。
最简单的方法:只需将类型中的 &'static str
更改为 String
即可编译。这样 HashMap
拥有密钥; serde 已经知道如何反序列化拥有的字符串。
static ref KEYWORDS: HashMap<String, i32> = { // ...
困难的方法:从技术上讲,您仍然可以通过泄漏 String
的后备缓冲区来获得 HashMap<&'static str, i32>
。通常 "leaking" 是不好的,但因为这是一个惰性静态,所以它真的没有什么区别,因为那些缓冲区永远不会被释放。通过泄漏 String
获得 &'static str
看起来像这样:
fn leak_string(from: String) -> &'static str {
Box::leak(from.into_boxed_str())
}
问题是 serde 不会自动执行此操作。实现此目的的一种方法是先反序列化为 HashMap<String, i32>
,然后通过获取每个条目并将它们插入新的 HashMap
之后将其转换为 HashMap<&'static string, i32>
运行通过 leak_string
的键。这是低效的,因为首先不需要收集到 HashMap
中。更好的解决方案是编写一个执行 leak_string
"on the fly" 的自定义反序列化器。由于简单的方法简单得多,而且这种困难的方法有一些绊脚石,我认为在这里提供完整的代码示例没有用。
"the hard way" 与 "the easy way" 的唯一真正优势是 "the hard way" 需要一个指针的价值更少的内存用于 HashMap
中的每个键(&str
是指针+len;String
是指针+len+容量)。它也很好,因为它不会改变你的类型签名,但是你可以用 &'static str
做的事情很少,而你不能用 String
.
这里的罪魁祸首是 serde_json::from_reader
的定义方式。来自 its documentation:
pub fn from_reader<R, T>(rdr: R) -> Result<T> where
R: Read,
T: DeserializeOwned,
所以,结果一定是自己的数据,不是借来的。即使 &'static
也不行。你必须在这里使用String
:
lazy_static! {
static ref KEYWORDS: HashMap<String, i32> = {
let file = File::open("words.json").unwrap();
let reader = BufReader::new(file);
serde_json::from_reader(reader).unwrap()
};
}
当使用 serde_json::from_str
从 &str
⟶ HashMap<&str, i32>
反序列化时,输入的 JSON 字符串需要比输出中的字符串切片长。这是'a
生命周期在签名中的作用:https://docs.rs/serde_json/1.0.40/serde_json/fn.from_str.html
这意味着如果输出需要包含具有 'static
生命周期的字符串切片,则输入 JSON 数据也必须具有 'static
生命周期。我们知道该怎么做 -- lazy_static!
use lazy_static::lazy_static;
use std::collections::HashMap;
lazy_static! {
static ref KEYWORDS: HashMap<&'static str, i32> = {
lazy_static! {
static ref WORDS_JSON: String = {
std::fs::read_to_string("words.json").unwrap()
};
}
serde_json::from_str(&WORDS_JSON).unwrap()
};
}