2017-10-01 14 views
10

Dans un projet où des méthodes personnalisées de sérialisation et de désérialisation de Serde (1.0) sont impliquées, je me suis appuyé sur cette routine de test pour vérifier si la sérialisation d'un objet et d'un retour produirait un objet équivalent.Comment pouvons-nous écrire une fonction générique pour vérifier la sérialisation Serde et la désérialisation?

// let o: T = ...; 
let buf: Vec<u8> = to_vec(&o).unwrap(); 
let o2: T = from_slice(&buf).unwrap(); 
assert_eq!(o, o2); 

Faire cela en ligne fonctionne plutôt bien. Ma prochaine étape vers la réutilisabilité était de faire une fonction check_serde à cet effet.

pub fn check_serde<T>(o: T) 
where 
    T: Debug + PartialEq<T> + Serialize + DeserializeOwned, 
{ 
    let buf: Vec<u8> = to_vec(&o).unwrap(); 
    let o2: T = from_slice(&buf).unwrap(); 
    assert_eq!(o, o2); 
} 

Cela fonctionne bien pour posséder types, mais pas pour les types avec des bornes à vie (Playground):

check_serde(5); 
check_serde(vec![1, 2, 5]); 
check_serde("five".to_string()); 
check_serde("wait"); // [E0279] 

L'erreur:

error[E0279]: the requirement `for<'de> 'de : ` is not satisfied (`expected bound lifetime parameter 'de, found concrete lifetime`) 
    --> src/main.rs:24:5 
    | 
24 |  check_serde("wait"); // [E0277] 
    |  ^^^^^^^^^^^ 
    | 
    = note: required because of the requirements on the impl of `for<'de> serde::Deserialize<'de>` for `&str` 
    = note: required because of the requirements on the impl of `serde::de::DeserializeOwned` for `&str` 
    = note: required by `check_serde` 

Comme je veux faire le travail de la fonction avec ces cas (y compris les structs avec des tranches de chaîne), j'ai tenté une nouvelle version avec une vie de désérialisation d'objet explicite:

pub fn check_serde<'a, T>(o: &'a T) 
where 
    T: Debug + PartialEq<T> + Serialize + Deserialize<'a>, 
{ 
    let buf: Vec<u8> = to_vec(o).unwrap(); 
    let o2: T = from_slice(&buf).unwrap(); 
    assert_eq!(o, &o2); 
} 

check_serde(&5); 
check_serde(&vec![1, 2, 5]); 
check_serde(&"five".to_string()); 
check_serde(&"wait"); // [E0405] 

Cette implémentation conduit à un autre problème et ne compilera pas (Playground).

error[E0597]: `buf` does not live long enough 
    --> src/main.rs:14:29 
    | 
14 |  let o2: T = from_slice(&buf).unwrap(); 
    |        ^^^ does not live long enough 
15 |  assert_eq!(o, &o2); 
16 | } 
    | - borrowed value only lives until here 
    | 
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 10:1... 
    --> src/main.rs:10:1 
    | 
10 |/pub fn check_serde<'a, T>(o: &'a T) 
11 | |  where T: Debug + PartialEq<T> + Serialize + Deserialize<'a> 
12 | | { 
13 | |  let buf: Vec<u8> = to_vec(o).unwrap(); 
14 | |  let o2: T = from_slice(&buf).unwrap(); 
15 | |  assert_eq!(o, &o2); 
16 | | } 
    | |_^ 

J'ai déjà attendu celui-ci: cette version implique que le contenu sérialisé (et donc l'objet désérialisé) vit aussi longtemps que l'objet d'entrée, ce qui est faux. Le tampon est seulement destiné à vivre aussi longtemps que la portée de la fonction.

Ma troisième tentative vise à construire des versions appartenant à l'entrée d'origine, évitant ainsi le problème d'avoir un objet désérialisé avec des limites de vie différentes. Le trait ToOwned semble convenir à ce cas d'utilisation.

pub fn check_serde<'a, T: ?Sized>(o: &'a T) 
where 
    T: Debug + ToOwned + PartialEq<<T as ToOwned>::Owned> + Serialize, 
    <T as ToOwned>::Owned: Debug + DeserializeOwned, 
{ 
    let buf: Vec<u8> = to_vec(&o).unwrap(); 
    let o2: T::Owned = from_slice(&buf).unwrap(); 
    assert_eq!(o, &o2); 
} 

Cela rend le travail de fonction pour les tranches de chaîne simple maintenant, mais pas pour les objets composites les contenant (Playground):

check_serde(&5); 
check_serde(&vec![1, 2, 5]); 
check_serde(&"five".to_string()); 
check_serde("wait"); 
check_serde(&("There's more!", 36)); // [E0279] 

Encore une fois, nous tombons sur le même genre d'erreur que la première version:

error[E0279]: the requirement `for<'de> 'de : ` is not satisfied (`expected bound lifetime parameter 'de, found concrete lifetime`) 
    --> src/main.rs:25:5 
    | 
25 |  check_serde(&("There's more!", 36)); // [E0279] 
    |  ^^^^^^^^^^^ 
    | 
    = note: required because of the requirements on the impl of `for<'de> serde::Deserialize<'de>` for `&str` 
    = note: required because of the requirements on the impl of `for<'de> serde::Deserialize<'de>` for `(&str, {integer})` 
    = note: required because of the requirements on the impl of `serde::de::DeserializeOwned` for `(&str, {integer})` 
    = note: required by `check_serde` 

Accordée, je suis à perte. Comment pouvons-nous construire une fonction générique qui, en utilisant Serde, sérialise un objet et le désérialise dans un nouvel objet? En particulier, cette fonction peut-elle être effectuée dans Rust (stable ou nocturne), et si oui, quels ajustements à ma mise en œuvre manquent?

Répondre

5

Malheureusement, ce que vous avez besoin est une caractéristique tha t n'est pas encore implémenté dans Rust: types associés génériques.

Regardons une autre variante de check_serde:

pub fn check_serde<T>(o: T) 
where 
    for<'a> T: Debug + PartialEq<T> + Serialize + Deserialize<'a>, 
{ 
    let buf: Vec<u8> = to_vec(&o).unwrap(); 
    let o2: T = from_slice(&buf).unwrap(); 
    assert_eq!(o, o2); 
} 

fn main() { 
    check_serde("wait"); // [E0279] 
} 

Le problème ici est que o2 ne peut pas être de type T: o2 fait référence à buf, qui est une variable locale, mais les paramètres de type ne peuvent pas être inféré types contraints par une durée de vie restreinte au corps de la fonction. Nous aimerions T être quelque chose comme &strsans une durée de vie spécifique attachée à elle.

Avec les types associés génériques, cela pourrait être résolu avec quelque chose comme ça (je ne peux évidemment pas le tester, car il est pas encore mis en œuvre):

trait SerdeFamily { 
    type Member<'a>: Debug + PartialEq<Self> + Serialize + Deserialize<'a>; 
} 

struct I32Family; 
struct StrFamily; 

impl SerdeFamily for I32Family { 
    type Member<'a> = i32; // we can ignore parameters 
} 

impl SerdeFamily for StrFamily { 
    type Member<'a> = &'a str; 
} 

pub fn check_serde<'a, Family>(o: Family::Member<'a>) 
where 
    Family: SerdeFamily, 
{ 
    let buf: Vec<u8> = to_vec(&o).unwrap(); 
    // `o2` is of type `Family::Member<'b>` 
    // with a lifetime 'b different from 'a 
    let o2: Family::Member = from_slice(&buf).unwrap(); 
    assert_eq!(o, o2); 
} 

fn main() { 
    check_serde::<I32Family>(5); 
    check_serde::<StrFamily>("wait"); 
} 
3

Le answer from Francis Gagné a montré que nous ne pouvons pas le faire efficacement sans types génériques associés. L'établissement de la propriété profonde de l'objet désérialisé est un travail possible que je décris ici.

La troisième tentative est très proche d'une solution flexible, mais elle est insuffisante en raison du fonctionnement de std::borrow::ToOwned. Le trait ne convient pas pour récupérer une version profondément possédée d'un objet. Tenter d'utiliser l'implémentation de ToOwned pour &str, par exemple, vous donne une autre tranche de chaîne.

let a: &str = "hello"; 
let b: String = (&a).to_owned(); // expected String, got &str 

De même, le type Owned pour une chaîne contenant des tranches de struct ne peut pas être un struct contenant String s. Dans le code:

#[derive(Debug, PartialEq, Serialize, Deserialize)] 
struct Foo<'a>(&str, i32); 

#[derive(Debug, PartialEq, Serialize, Deserialize)] 
struct FooOwned(String, i32); 

Nous ne pouvons pas impl ToOwned pour Foo de fournir FooOwned parce que:

  • Si nous dérivons Clone, la mise en œuvre de ToOwned pour T: Clone est uniquement applicable aux Owned = Self.
  • Même avec une implémentation personnalisée de ToOwned, le trait nécessite que le type appartenant puisse être emprunté dans le type d'origine (en raison de la contrainte Owned: Borrow<Self>). Autrement dit, nous sommes censés être en mesure de récupérer un &Foo(&str, i32) sur un FooOwned, mais leur structure interne est différente, et donc ce n'est pas réalisable.

Cela signifie que, pour suivre la troisième approche, nous avons besoin d'un trait différent. Avons un nouveau trait ToDeeplyOwned qui transforme un objet en un entièrement possédé, sans tranches ou références impliquées.

pub trait ToDeeplyOwned { 
    type Owned; 
    fn to_deeply_owned(&self) -> Self::Owned; 
} 

L'intention ici est de produire une copie profonde de tout. Il ne semble pas y avoir de mise en œuvre facile, mais certaines astuces sont possibles. Tout d'abord, nous pouvons l'implémenter à tous les types de référence où T: ToDeeplyOwned. À ce stade, nous devrions l'appliquer de façon sélective aux types sans référence où nous savons que c'est OK. J'ai écrit une macro pour rendre ce processus moins verbeux, qui utilise to_owned() en interne.

macro_rules! impl_deeply_owned { 
    ($t: ty, $t2: ty) => { // turn $t into $t2 
     impl ToDeeplyOwned for $t { 
      type Owned = $t2; 
      fn to_deeply_owned(&self) -> Self::Owned { 
       self.to_owned() 
      } 
     } 
    }; 
    ($t: ty) => { // turn $t into itself, self-contained type 
     impl ToDeeplyOwned for $t { 
      type Owned = $t; 
      fn to_deeply_owned(&self) -> Self::Owned { 
       self.to_owned() 
      } 
     } 
    }; 
} 

Pour les exemples de la question au travail, nous avons besoin d'au moins ceux-ci:

impl_deeply_owned!(i32); 
impl_deeply_owned!(String); 
impl_deeply_owned!(Vec<i32>); 
impl_deeply_owned!(str, String); 

Une fois que nous mettons en œuvre les traits nécessaires sur Foo/FooOwned et d'adapter serde_check à utiliser le nouveau trait, le code maintenant compile et fonctionne avec succès (Playground):

#[derive(Debug, PartialEq, Serialize)] 
struct Foo<'a>(&'a str, i32); 

#[derive(Debug, PartialEq, Clone, Deserialize)] 
struct FooOwned(String, i32); 

impl<'a> ToDeeplyOwned for Foo<'a> { 
    type Owned = FooOwned; 

    fn to_deeply_owned(&self) -> FooOwned { 
     FooOwned(self.0.to_string(), self.1) 
    } 
} 

impl<'a> PartialEq<FooOwned> for Foo<'a> { 
    fn eq(&self, o: &FooOwned) -> bool { 
     self.0 == o.0 && self.1 == o.1 
    } 
} 

pub fn check_serde<'a, T: ?Sized>(o: &'a T) 
where 
    T: Debug + ToDeeplyOwned + PartialEq<<T as ToDeeplyOwned>::Owned> + Serialize, 
    <T as ToDeeplyOwned>::Owned: Debug + DeserializeOwned, 
{ 
    let buf: Vec<u8> = to_vec(&o).unwrap(); 
    let o2: T::Owned = from_slice(&buf).unwrap(); 
    assert_eq!(o, &o2); 
} 

// all of these are ok 
check_serde(&5); 
check_serde(&vec![1, 2, 5]); 
check_serde(&"five".to_string()); 
check_serde("wait"); 
check_serde(&"wait"); 
check_serde(&Foo("There's more!", 36));