我们如何编写用于检查Serde序列化和反序列化的通用函数?

时间:2017-10-01 16:12:10

标签: serialization rust lifetime serde

在涉及自定义Serde(1.0)序列化和反序列化方法的项目中,我依赖此测试例程来检查序列化对象和返回是否会产生等效对象。

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

这样的内联工作非常好。我对可重用性的下一步是为此目的创建一个函数check_serde

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

这适用于拥有类型,但不适用于具有生命周期界限的类型(Playground):

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

错误:

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`

由于我希望使函数适用于这些情况(包括带有字符串切片的结构),我尝试了一个具有显式对象反序列化生命周期的新版本:

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]

此实现导致另一个问题,它不会编译(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 | | }
   | |_^

我已经预料到了这个:这个版本暗示序列化内容(以及反序列化对象)与输入对象一样长,这是不正确的。缓冲区只能在函数范围内存活。

我的第三次尝试旨在构建原始输入的自有版本,从而避免了具有不同生命周期边界的反序列化对象的问题。 ToOwned特征似乎适合这种用例。

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

这使得该函数现在可用于普通字符串切片,但不适用于包含它们的复合对象(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]

同样,我们偶然发现与第一个版本相同的错误类型:

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`

当然,我很茫然。我们如何构建一个泛型函数,使用Serde序列化一个对象并将其反序列化为一个新对象?特别是,这个函数可以在Rust中生成(稳定还是每晚),如果是这样,我的实现有哪些调整?

4 个答案:

答案 0 :(得分:5)

不幸的是,您需要的是一个尚未在Rust中实现的功能:通用关联类型。

让我们看一下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]
}

此处的问题是o2不能是T类型:o2是指buf,它是一个局部变量,但类型参数不能推断为类型被限制在功能体内的一生约束。我们希望T类似于&str ,而不附加特定的生命周期。

使用通用关联类型,可以通过这样的方式解决(显然我无法对其进行测试,因为它还没有实现):

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

答案 1 :(得分:4)

answer from Francis Gagné表明,如果没有通用的关联类型,我们就无法做到这一点。建立对反序列化对象的深层所有权是我在这里描述的一种可能的解决方法。

第三次尝试与灵活的解决方案非常接近,但由于std::borrow::ToOwned的工作方式不尽如人意。该特征不适合检索深度拥有的对象版本。例如,尝试使用ToOwned for &str的实现为您提供另一个字符串切片。

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

同样,包含字符串切片的结构的Owned类型不能是包含String的结构。在代码中:

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

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

我们无法为ToOwned提供Foo来提供FooOwned,因为:

  • 如果我们推导Clone,则ToOwned T: Clone的实施仅适用于Owned = Self
  • 即使使用ToOwned的自定义实现,该特征也要求所拥有的类型可以借用到原始类型中(由于约束Owned: Borrow<Self>)。也就是说,我们应该能够从&Foo(&str, i32)中检索FooOwned,但它们的内部结构不同,所以这是不可能实现的。

这意味着,为了遵循第三种方法,我们需要一个不同的特征。让我们有一个新的特征ToDeeplyOwned,它将一个对象变成一个完全拥有的特征,不涉及切片或引用。

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

这里的目的是从任何东西中生成一份深层副本。似乎没有一个简单的全部实现,但一些技巧是可能的。首先,我们可以将其实现到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()
    }
}

此时我们必须有选择地将它实现为非引用类型,我们知道它没问题。我写了一个宏来使这个过程更简洁,内部使用to_owned()

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

对于问题中的示例,我们至少需要这些:

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

我们在Foo / FooOwned上实施必要的特征并调整serde_check以使用新特征后,代码现在可以编译并成功运行(Playground):< / p>

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

答案 2 :(得分:2)

截至目前,可以在每晚使用 Rust 实现此功能(使用明确的差异解决方法):

#![feature(generic_associated_types)]

use serde::{Deserialize, Serialize};
use serde_json::{from_slice, to_vec};
use std::fmt::Debug;

trait SerdeFamily {
    type Member<'a>: Debug + PartialEq + Serialize + Deserialize<'a>;
    
    // https://internals.rust-lang.org/t/variance-of-lifetime-arguments-in-gats/14769/19
    fn upcast_gat<'short, 'long: 'short>(long: Self::Member<'long>) -> Self::Member<'short>;
}

struct I32Family;
struct StrFamily;

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

    fn upcast_gat<'short, 'long: 'short>(long: Self::Member<'long>) -> Self::Member<'short> {
        long
    }
}

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

    fn upcast_gat<'short, 'long: 'short>(long: Self::Member<'long>) -> Self::Member<'short> {
        long
    }
}

fn check_serde<F: SerdeFamily>(o: F::Member<'_>) {
    let buf: Vec<u8> = to_vec(&o).unwrap();
    let o2: F::Member<'_> = from_slice(&buf).unwrap();
    assert_eq!(F::upcast_gat(o), o2);
}

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

Playground

答案 3 :(得分:1)

简单(但有点笨拙)的解决方案:从函数外部提供 buf

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

buf 可以与 Cursor

重用
pub fn check_serde_with_cursor<'a, T>(o: &'a T, buf: &'a mut Vec<u8>)
where
    T: Debug + PartialEq<T> + Serialize + Deserialize<'a>,
{
    buf.clear();
    let mut cursor = Cursor::new(buf);
    to_writer(&mut cursor, o).unwrap();
    let o2: T = from_slice(cursor.into_inner()).unwrap();
    assert_eq!(o, &o2);
}