/ / Jak możemy napisać ogólną funkcję do sprawdzania serializacji i deserializacji Serde? - serializacja, rdza, całe życie, serde

Jak możemy napisać ogólną funkcję do sprawdzania serializacji i deserializacji Serde? - serializacja, rdza, życie, serde

W projekcie, w którym niestandardowy Serde (1.0) w grę wchodzą metody serializacji i deserializacji. Oparłem się na tej procedurze testowej, aby sprawdzić, czy serializacja obiektu i powrót da równoważny obiekt.

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

Robienie tego w wierszu działa całkiem dobrze. Moim następnym krokiem w kierunku ponownego użycia było stworzenie funkcji check_serde w tym celu.

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

Działa to dobrze w przypadku posiadania typów, ale nie w przypadku typów z limitami czasu życia (Plac zabaw):

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

Błąd:

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`

Ponieważ chcę, aby funkcja działała w tych przypadkach (w tym strukturach z wycinkami łańcucha), próbowałem nowej wersji z okresem istnienia deserializacji jawnego obiektu:

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]

Ta implementacja prowadzi do kolejnego problemu i nie można jej „skompilować (Plac zabaw).

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

Oczekiwałem już tego: ta wersja sugeruje, że treść serializowana (a więc zdejrializowany obiekt) żyje tak długo, jak obiekt wejściowy, co nie jest prawdą. Bufor ma żyć tylko tak długo, jak zasięg funkcji.

Moja trzecia próba ma na celu zbudowanie posiadanych wersji oryginalnych danych wejściowych, unikając w ten sposób problemu zdezrializowanego obiektu o różnych granicach życia. The ToOwned Wydaje się, że cecha pasuje do tego przypadku użycia.

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

To sprawia, że ​​funkcja działa teraz dla plasterków zwykłego ciągu znaków, ale nie dla zawierających je obiektów złożonych (Plac zabaw):

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

Znów natrafiamy na ten sam rodzaj błędu, co pierwsza wersja:

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`

To prawda, że ​​jestem zagubiony. Jak możemy zbudować ogólną funkcję, która za pomocą Serde serializuje obiekt i przekształca go z powrotem w nowy obiekt? W szczególności, czy ta funkcja może być wykonana w Rust (stabilna lub nocna), a jeśli tak, jakich korekt w mojej implementacji brakuje?

Odpowiedzi:

5 dla odpowiedzi № 1

Niestety, potrzebna jest funkcja, która nie jest jeszcze zaimplementowana w Rust: ogólne typy skojarzone.

Spójrzmy na inny wariant 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]
}

Problem polega na tym o2 nie może być typu T: o2 odnosi się do buf, która jest zmienną lokalną, ale parametrów typu nie można wywnioskować z typów ograniczonych czasem życia ograniczonym do ciała funkcji. T być czymś takim &str bez określone życie związane z tym.

W przypadku ogólnych typów powiązanych można to rozwiązać za pomocą czegoś takiego (oczywiście nie mogę tego przetestować, ponieważ nie został jeszcze zaimplementowany):

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 dla odpowiedzi № 2

The odpowiedź Francisa Gagné pokazał, że nie możemy tego zrobić skutecznie bez ogólnych typów powiązanych. Ustanowienie głębokiej własności zdejrializowanego obiektu jest możliwym obejściem, które tutaj opiszę.

Trzecia próba jest bardzo zbliżona do elastycznego rozwiązania, ale nie wystarcza ze względu na to, jak std::borrow::ToOwned Pracuje. Ta cecha nie nadaje się do wyszukiwania głęboko posiadanej wersji obiektu. Próba użycia implementacji ToOwned dla &str, na przykład, daje ci kolejny ciąg znaków.

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

Podobnie Owned typ dla struktury zawierającej wycinki łańcuchowe nie może być strukturą zawierającą Strings. W kodzie:

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

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

Nie możemy impl ToOwned dla Foo zapewnić FooOwned bo:

  • Jeśli wyprowadzimy Clone, implementacja ToOwned dla T: Clone dotyczy tylko Owned = Self.
  • Nawet przy niestandardowej implementacji ToOwned, cecha wymaga, aby posiadany typ mógł zostać pożyczony do oryginalnego typu (z powodu ograniczenia Owned: Borrow<Self>). Oznacza to, że powinniśmy być w stanie odzyskać &Foo(&str, i32) z a FooOwned, ale ich wewnętrzna struktura jest inna, więc nie jest to możliwe.

Oznacza to, że aby zastosować trzecie podejście, potrzebujemy innej cechy. Miejmy nową cechę ToDeeplyOwned co powoduje, że obiekt staje się całkowicie własnością, bez żadnych wycinków ani odniesień.

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

Chodzi tutaj o stworzenie głębokiej kopii zbyle co. Wydaje się, że nie jest to łatwa implementacja typu catch-all, ale możliwe są pewne sztuczki. Po pierwsze, możemy zaimplementować ją do wszystkich typów referencyjnych, w których 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()
}
}

W tym momencie musielibyśmy selektywnie zaimplementować go w typach niereferencyjnych, o których wiemy, że jest w porządku. Napisałem makro, aby ten proces był mniej szczegółowy, który wykorzystuje to_owned() wewnętrznie.

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

Aby przykłady w pytaniu działały, potrzebujemy co najmniej tych:

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

Po wdrożeniu niezbędnych cech Foo/FooOwned i dostosuj się serde_check aby użyć nowej cechy, kod teraz kompiluje się i działa pomyślnie (Plac zabaw):

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