Как я могу использовать Serde для десериализации структур со ссылками от читателя? - PullRequest
3 голосов
/ 22 марта 2020

У меня есть эти структуры:

#[derive(Debug, Serialize, Deserialize)]
pub struct GGConf<'a> {
    #[serde(alias = "ssh")]
    #[serde(rename = "ssh")]
    #[serde(default)]
    #[serde(borrow)]
    pub ssh_config: Option<SSHConfig<'a>>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SSHConfig<'a> {
    #[serde(alias = "privateKey")]
    #[serde(rename = "privateKey")]
    private_key: &'a str,

    username: &'a str,
}

Десериализация происходит, когда я читаю из файла YAML:

let mut config: GGConf = serde_yaml::from_reader(file)?;

При компиляции я получаю ошибку:

error: implementation of `conf::_IMPL_DESERIALIZE_FOR_GGConf::_serde::Deserialize` is not general enough
   --> src/conf.rs:50:34
    |
50  |           let mut config: GGConf = serde_yaml::from_reader(file)?;
    |                                    ^^^^^^^^^^^^^^^^^^^^^^^ implementation of `conf::_IMPL_DESERIALIZE_FOR_GGConf::_serde::Deserialize` is not general enough
    |
   ::: /home/ninan/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.98/src/de/mod.rs:524:1
    |
524 | / pub trait Deserialize<'de>: Sized {
525 | |     /// Deserialize this value from the given Serde deserializer.
526 | |     ///
527 | |     /// See the [Implementing `Deserialize`][impl-deserialize] section of the
...   |
562 | |     }
563 | | }
    | |_- trait `conf::_IMPL_DESERIALIZE_FOR_GGConf::_serde::Deserialize` defined here
    |
    = note: `conf::GGConf<'_>` must implement `conf::_IMPL_DESERIALIZE_FOR_GGConf::_serde::Deserialize<'0>`, for any lifetime `'0`...
    = note: ...but `conf::GGConf<'_>` actually implements `conf::_IMPL_DESERIALIZE_FOR_GGConf::_serde::Deserialize<'1>`, for some specific lifetime `'1`

Я смутно понимаю, что десериализация serde также имеет время жизни 'de и что компилятор сбивает с толку мое время жизни, указанное для него? Пожалуйста, поправьте меня, если я ошибаюсь.

Как в настоящее время правильно десериализовать YAML в обе структуры? Есть ли что-то, что я здесь упускаю или неправильно понял?

Я посмотрел на Как мне решить «реализация serde :: Deserialize недостаточно универсальна» с типом Json actix-web? , но я не могу использовать собственный тип. Мне нужно, чтобы это был заимствованный тип.

Я постараюсь написать для этого пример игровой площадки.

Ответы [ 2 ]

6 голосов
/ 25 марта 2020

Это невозможно; Вы должны использовать собственные данные вместо ссылок.

Вот минимальный пример:

use serde::Deserialize; // 1.0.104

#[derive(Debug, Deserialize)]
pub struct SshConfig<'a> {
    username: &'a str,
}

fn example(file: impl std::io::Read) {
    serde_yaml::from_reader::<_, SshConfig>(file);
}
error: implementation of `_IMPL_DESERIALIZE_FOR_SshConfig::_serde::Deserialize` is not general enough
   --> src/lib.rs:9:5
    |
9   |       serde_yaml::from_reader::<_, SshConfig>(file);
    |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `_IMPL_DESERIALIZE_FOR_SshConfig::_serde::Deserialize` is not general enough
    | 
   ::: /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.104/src/de/mod.rs:531:1
    |
531 | / pub trait Deserialize<'de>: Sized {
532 | |     /// Deserialize this value from the given Serde deserializer.
533 | |     ///
534 | |     /// See the [Implementing `Deserialize`][impl-deserialize] section of the
...   |
569 | |     }
570 | | }
    | |_- trait `_IMPL_DESERIALIZE_FOR_SshConfig::_serde::Deserialize` defined here
    |
    = note: `SshConfig<'_>` must implement `_IMPL_DESERIALIZE_FOR_SshConfig::_serde::Deserialize<'0>`, for any lifetime `'0`...
    = note: ...but `SshConfig<'_>` actually implements `_IMPL_DESERIALIZE_FOR_SshConfig::_serde::Deserialize<'1>`, for some specific lifetime `'1`

Если вы посмотрите на определение serde_yaml::from_reader, вы Вы увидите, что он ограничен только десериализацией принадлежащих данных:

pub fn from_reader<R, T>(rdr: R) -> Result<T>
where
    R: Read,
    T: DeserializeOwned,
//     ^^^^^^^^^^^^^^^^ 

То же самое верно для serde_json::from_reader и, возможно, любой эквивалентной функции.

Вы можете только десериализовать тип, содержащий ссылки , когда есть данные для ссылки . Нечто, реализующее черту Read, гарантирует только то, что оно может скопировать некоторые байты в предоставленный пользователем буфер. Поскольку функция from_reader не принимает этот буфер в качестве аргумента, любой буфер будет уничтожен при выходе из from_reader, что сделает недействительными ссылки.

См. Также:


Если вы должны использовать ссылки (а во многих случаях это не так), вам необходимо:

  1. читать из ридера самостоятельно в буфер
  2. использовать from_str вместо from_reader
  3. хранить буфер так долго, как десериализованные данные
2 голосов
/ 30 марта 2020

from_reader получает поток данных откуда-то (где-либо, где реализуется признак чтения) - он не хранит данные, то есть ничто не владеет данными, поэтому вы не можете иметь ссылку на эти данные в своей структуре. Другими словами, from_reader принимает временный поток данных, поэтому ему необходимо место для хранения данных.

Дополнительным осложнением является то, что serde_yaml (по крайней мере для версии 0.8.11) не поддержка десериализации нулевой копии:

https://docs.rs/serde_yaml/0.8.11/serde_yaml/fn.from_str.html

pub fn from_str<T>(s: &str) -> Result<T> where
    T: DeserializeOwned,

...

В настоящее время YAML не поддерживает нулевую копию десериализация.

Сравните это, скажем, с serde_json, что:

https://docs.rs/serde_json/1.0.50/serde_json/de/fn.from_str.html

pub fn from_str<'a, T>(s: &'a str) -> Result<T> where
    T: Deserialize<'a>,

Итак, по крайней мере, с чем-то вроде serde_json вы можете использовать from_str из собственного буфера, и это позволит вам использовать ссылки в вашей структуре (но это не будет работать для serde_yaml в настоящее время)

// Written with rustc 1.42.0 and
// [dependencies]
// serde = "1.0.105"
// serde_derive = "1.0.105"
// serde_json = "1.0.50"

use std::io::Read;
use serde_derive::Deserialize;

#[derive(Debug, Deserialize)]
pub struct SshConfig<'a> {
    username: &'a str,
}

fn main() {
    // Open file handle
    let mut file = std::fs::File::open("example.json").unwrap();

    // Read the data into a String, which stores (and thus owns) the data
    let mut strbuf = String::new();
    file.read_to_string(&mut strbuf).unwrap();

    // Deserialize into struct, which references
    let result: SshConfig = serde_json::from_str(&strbuf).unwrap();
    println!("{:?}", result.username);

    // Note that `result` is only valid as long as `strbuf` exists.
    // i.e if `strbuf` goes out of scope or is moved to another function, we get an error. For example, the following would cause an error:
    // std::mem::drop(strbuf); // Function which moves strbuf, not a referernce
    // println!("{:?}", result.username); // Error
}

В зависимости от того, что именно вас беспокоит, это может быть менее эффективно, чем сохранение строки в вашей структуре (например, если example.json имеет размер 1 МБ и вы извлекаете только одно поле - приведенный выше код будет хранить всю строку размером 1 МБ в памяти только для того, чтобы текст был доступен на несколько байтов).

...