修复使用 lazy_static 时的 serde 生命周期问题

Fix serde lifetime issue when using lazy_static

我想将一些 json 读入静态 HashMap,并且正在使用 lazy_staticserde,但我不知道如何(如果有的话)我可以解决这个 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()
    };
}

playground link

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&strHashMap<&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()
    };
}