Исправлена ​​проблема времени жизни serde при использовании lazy_static - PullRequest
0 голосов
/ 20 сентября 2019

Я хотел бы прочитать некоторые 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 isпростая карта JSON: {"aaargh": 1}.

Я открыт для другого, не lazy_static подхода в случае необходимости.

Ответы [ 3 ]

1 голос
/ 21 сентября 2019

При использовании 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()
    };
}
0 голосов
/ 20 сентября 2019

Виновным здесь является то, как определяется serde_json::from_reader.Из его документации :

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()
    };
}
0 голосов
/ 20 сентября 2019

Сообщение об ошибке говорит о том, что вы не можете десериализовать до &'static str.Поскольку десериализатор продолжает создавать записи, ключи &str могут иметь столько же времени жизни, сколько заимствовано из буфера, в который десериализатор читает файл.Но &'static str должен указывать на str, который живет вечно.

Здесь я вижу два решения: легкий и трудный путь.

легкий путь: просто поменяйте &'static strв типе String и он компилируется.Таким образом, HashMap владеет ключами;serde уже знает, как десериализовать собственные строки.

static ref KEYWORDS: HashMap<String, i32> = { // ...

Сложный путь: Технически вы все еще можете получить свой HashMap<&'static str, i32>, пропуская резервные буферы String s.Обычно «утечка» - это плохо, но, поскольку это ленивая статика, это действительно не имеет значения, так как эти буферы никогда не будут освобождены.Получение &'static str путем утечки String выглядит следующим образом:

fn leak_string(from: String) -> &'static str {
    Box::leak(from.into_boxed_str())
}

Проблема в том, что serde не делает этого автоматически.Один из способов сделать это - сначала десериализовать в HashMap<String, i32>, а затем преобразовать в HashMap<&'static string, i32>, взяв каждую из записей и вставив их в новый HashMap после запуска ключей через leak_string.Это неэффективно, так как не было необходимости собирать в HashMap в первую очередь.Лучшим решением было бы написать собственный десериализатор, который выполнял бы leak_string «на лету».Поскольку легкий путь намного проще, и для этого трудного пути есть несколько камней преткновения, я не думаю, что здесь будет полезен полный пример кода.

Единственное реальное преимущество «трудного пути»По сравнению с «простым способом», «сложный путь» требует меньше памяти на один указатель для каждой клавиши в HashMap (&str - указатель + len; String - указатель + len + емкость).Приятно также и то, что он не меняет сигнатуру вашего типа, но вы мало что можете сделать с &'static str, чего не можете сделать с String.

...