/ / Wie können wir eine generische Funktion zum Überprüfen der Serde-Serialisierung und -Deserialisierung schreiben? - Serialisierung, Rost, Lebensdauer, Serde

Wie können wir eine generische Funktion zum Überprüfen der Serde-Serialisierung und Deserialisierung schreiben? - Serialisierung, Rost, Lebensdauer, Serde

In einem Projekt, in dem benutzerdefinierte Serde (1.0) Serialisierungs- und Deserialisierungsmethoden sind beteiligt. Ich habe mich auf diese Testroutine verlassen, um zu überprüfen, ob die Serialisierung eines Objekts und zurück ein äquivalentes Objekt ergeben würde.

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

Dies inline zu tun funktioniert ziemlich gut. Mein nächster Schritt in Richtung Wiederverwendbarkeit bestand darin, eine Funktion zu erstellen check_serde für diesen Zweck.

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);
}

Dies funktioniert gut für den Besitz von Typen, jedoch nicht für Typen mit lebenslangen Grenzen (Spielplatz):

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

Der Fehler:

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`

Da ich möchte, dass die Funktion in diesen Fällen funktioniert (einschließlich Strukturen mit String-Slices), habe ich versucht, eine neue Version mit einer expliziten Lebensdauer für die Deserialisierung von Objekten zu erstellen:

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]

Diese Implementierung führt zu einem anderen Problem und wird nicht kompiliert (Spielplatz).

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 | | }
| |_^

Ich habe diesen schon erwartet: Diese Version impliziert, dass der serialisierte Inhalt (und damit das deserialisierte Objekt) so lange lebt wie das Eingabeobjekt, was nicht wahr ist. Der Puffer soll nur so lange leben wie der Funktionsumfang.

Mein dritter Versuch versucht, eigene Versionen der ursprünglichen Eingabe zu erstellen, um das Problem eines deserialisierten Objekts mit unterschiedlichen Lebensdauergrenzen zu umgehen. Das ToOwned Das Merkmal scheint diesem Anwendungsfall zu entsprechen.

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);
}

Dadurch funktioniert die Funktion jetzt für einfache Zeichenfolgenschnitte, jedoch nicht für zusammengesetzte Objekte, die sie enthalten (Spielplatz):

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

Wieder stoßen wir auf die gleiche Fehlerart wie die erste 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`

Zugegeben, ich bin ratlos. Wie können wir eine generische Funktion erstellen, die mit Serde ein Objekt serialisiert und es wieder in ein neues Objekt deserialisiert? Kann diese Funktion insbesondere in Rust (stabil oder nächtlich) ausgeführt werden, und wenn ja, welche Anpassungen an meiner Implementierung fehlen?

Antworten:

5 für die Antwort № 1

Leider benötigen Sie eine Funktion, die in Rust noch nicht implementiert ist: generische zugeordnete Typen.

Schauen wir uns eine andere Variante von an 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]
}

Das Problem ist, dass o2 kann nicht vom Typ sein T: o2 bezieht sich auf bufDies ist eine lokale Variable, aber Typparameter können nicht auf Typen abgeleitet werden, die durch eine Lebensdauer eingeschränkt sind, die auf den Körper der Funktion beschränkt ist. Wir möchten für T etwas wie sein &str ohne eine bestimmte Lebensdauer damit verbunden.

Mit generischen zugehörigen Typen könnte dies mit so etwas gelöst werden (offensichtlich kann ich es nicht testen, da es noch nicht implementiert ist):

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 für die Antwort № 2

Das Antwort von Francis Gagné hat gezeigt, dass wir dies ohne generische zugehörige Typen nicht effizient tun können. Eine mögliche Umgehung, die ich hier beschreibe, besteht darin, ein tiefes Eigentum an dem deserialisierten Objekt herzustellen.

Der dritte Versuch kommt einer flexiblen Lösung sehr nahe, ist aber aufgrund dessen nicht ausreichend std::borrow::ToOwned funktioniert. Das Merkmal ist nicht zum Abrufen einer Version eines Objekts in tiefem Besitz geeignet. Versuch, die Implementierung von ToOwned für zu verwenden &strgibt Ihnen zum Beispiel ein weiteres String-Slice.

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

Ebenso, die Owned Typ für eine Struktur, die String-Slices enthält, kann keine Struktur sein, die enthält Strings. In Code:

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

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

Wir können nicht implizieren ToOwned zum Foo bereitstellen FooOwned weil:

  • Wenn wir ableiten Clone, Die Implementierung von ToOwned zum T: Clone gilt nur für Owned = Self.
  • Auch bei einer kundenspezifischen Implementierung von ToOwnedDas Merkmal erfordert, dass der besessene Typ (aufgrund der Einschränkung) in den ursprünglichen Typ ausgeliehen werden kann Owned: Borrow<Self>). Das heißt, wir sollen in der Lage sein, a abzurufen &Foo(&str, i32) aus einem FooOwned, aber ihre innere Struktur ist anders, und so ist dies nicht erreichbar.

Dies bedeutet, dass wir, um dem dritten Ansatz zu folgen, ein anderes Merkmal benötigen. Lassen Sie uns ein neues Merkmal haben ToDeeplyOwned Dadurch wird ein Objekt zu einem vollständigen Objekt, ohne dass Slices oder Referenzen erforderlich sind.

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

Die Absicht hier ist es, eine tiefe Kopie von zu produzierenetwas. Es scheint keine einfache Gesamtimplementierung zu geben, aber einige Tricks sind möglich. Erstens können wir sie für alle Referenztypen implementieren, bei denen T: ToDeeplyOwned.

impl<"a, T: ?Sized + ToDeeplyOwned> ToDeeplyOwned for &"a T {
type Owned = T::Owned;
fn to_deeply_owned(&self) -> Self::Owned {
(**self).to_deeply_owned()
}
}

An diesem Punkt müssten wir es selektiv in Nichtreferenztypen implementieren, bei denen wir wissen, dass es in Ordnung ist. Ich habe ein Makro geschrieben, um diesen Prozess weniger ausführlich zu gestalten, das verwendet to_owned() im Inneren.

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

Damit die Beispiele in der Frage funktionieren, benötigen wir mindestens diese:

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

Sobald wir die notwendigen Eigenschaften umgesetzt haben Foo/FooOwned und anpassen serde_check Um das neue Merkmal zu verwenden, wird der Code jetzt kompiliert und erfolgreich ausgeführt (Spielplatz):

#[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));