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 № 1Leider 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 buf
Dies 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 &str
gibt 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 String
s. 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 vonToOwned
zumT: Clone
gilt nur fürOwned = Self
. - Auch bei einer kundenspezifischen Implementierung von
ToOwned
Das Merkmal erfordert, dass der besessene Typ (aufgrund der Einschränkung) in den ursprünglichen Typ ausgeliehen werden kannOwned: Borrow<Self>
). Das heißt, wir sollen in der Lage sein, a abzurufen&Foo(&str, i32)
aus einemFooOwned
, 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));