/ / ¿Cómo podemos escribir una función genérica para verificar la serialización y deserialización de Serde? - serialización, herrumbre, vida útil, serde

¿Cómo podemos escribir una función genérica para verificar la serialización y deserialización de Serde? - serialización, herrumbre, vida útil, serde

En un proyecto donde se personalizó Serde (1.0) Los métodos de serialización y deserialización están involucrados, me he basado en esta rutina de prueba para verificar si la serialización de un objeto y la de vuelta produciría un objeto equivalente.

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

Hacer esto en línea funciona bastante bien. Mi siguiente paso hacia la reutilización fue hacer una función. check_serde para este propósito.

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

Esto funciona bien para poseer tipos, pero no para tipos con límites de por vida (Patio de recreo)

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

El error:

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`

Como deseo hacer que la función funcione con estos casos (incluidas las estructuras con cortes de cadena), intenté una nueva versión con un tiempo de vida de deserialización de objetos explícito:

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]

Esta implementación lleva a otro problema, y ​​no se compiló (Patio de recreo)

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

Ya he esperado este: esta versión implica que el contenido serializado (y por lo tanto el objeto deserializado) vive tanto como el objeto de entrada, lo cual no es cierto. El búfer solo está destinado a vivir mientras el alcance de la función.

Mi tercer intento busca construir versiones propias de la entrada original, evitando así el problema de tener un objeto deserializado con diferentes límites de por vida. los ToOwned El rasgo parece adaptarse a este caso de uso.

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

Esto hace que la función funcione para cortes de cadena sin formato ahora, pero no para los objetos compuestos que los contienen (Patio de recreo)

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

Nuevamente, nos topamos con el mismo tipo de error que la primera versión:

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`

Concedido, estoy perdido. ¿Cómo podemos construir una función genérica que, utilizando Serde, serializa un objeto y lo deserializa en un nuevo objeto? En particular, ¿se puede realizar esta función en Rust (estable o nocturna) y, de ser así, qué ajustes faltan en mi implementación?

Respuestas

5 para la respuesta № 1

Desafortunadamente, lo que necesita es una característica que aún no está implementada en Rust: tipos genéricos asociados.

Veamos una variante diferente 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]
}

El problema aquí es que o2 no puede ser de tipo T: o2 se refiere a buf, que es una variable local, pero los parámetros de tipo no se pueden inferir a tipos restringidos por un tiempo de vida que está restringido al cuerpo de la función. T ser algo como &str sin una vida específica adjunta a ella.

Con los tipos genéricos asociados, esto podría resolverse con algo como esto (obviamente no puedo probarlo, ya que todavía no está implementado):

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 para la respuesta № 2

los respuesta de francis gagné ha demostrado que no podemos hacerlo de manera eficiente sin los tipos genéricos asociados. Establecer una propiedad profunda del objeto deserializado es una posible solución que describo aquí.

El tercer intento es muy cercano a una solución flexible, pero se queda corto debido a cómo std::borrow::ToOwned trabajos. El rasgo no es adecuado para recuperar una versión de propiedad profunda de un objeto. Intentando utilizar la implementación de ToOwned para &str, por ejemplo, te da otra porción de cadena.

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

Del mismo modo, el Owned El tipo para una estructura que contiene segmentos de cadena no puede ser una estructura que contiene Strings. En codigo:

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

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

No podemos implorar ToOwned para Foo Para proveer FooOwned porque:

  • Si derivamos Clone, la implementación de ToOwned para T: Clone solo es aplicable a Owned = Self.
  • Incluso con una implementación personalizada de ToOwned, el rasgo requiere que el tipo de propiedad se pueda tomar prestado en el tipo original (debido a la restricción Owned: Borrow<Self>). Es decir, se supone que debemos poder recuperar un &Foo(&str, i32) fuera de un FooOwned, pero su estructura interna es diferente, y por lo tanto esto no es alcanzable.

Esto significa que, para seguir el tercer enfoque, necesitamos un rasgo diferente. Tengamos un nuevo rasgo ToDeeplyOwned que convierte un objeto en uno de propiedad total, sin rebanadas ni referencias involucradas.

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

La intención aquí es producir una copia profunda decualquier cosa. No parece haber una implementación fácil de todos, pero algunos trucos son posibles. Primero, podemos implementarlo en todos los tipos de referencia donde 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()
}
}

En este punto, tendríamos que implementarlo de forma selectiva para los tipos que no son de referencia en los que sabemos que está bien. Escribí una macro para hacer este proceso menos detallado, que utiliza to_owned() internamente.

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

Para que los ejemplos de la pregunta funcionen, necesitamos al menos estos:

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

Una vez que implementamos los rasgos necesarios en Foo/FooOwned y adaptar serde_check para usar el nuevo rasgo, el código ahora se compila y ejecuta con éxito (Patio de recreo)

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