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

E_n*_*ate 12 serialization lifetime rust 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);
Run Code Online (Sandbox Code Playgroud)

这样的内联工作非常好.我对可重用性的下一步是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);
}
Run Code Online (Sandbox Code Playgroud)

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

check_serde(5);
check_serde(vec![1, 2, 5]);
check_serde("five".to_string());
check_serde("wait"); // [E0279]
Run Code Online (Sandbox Code Playgroud)

错误:

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`
Run Code Online (Sandbox Code Playgroud)

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

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]
Run Code Online (Sandbox Code Playgroud)

此实现导致另一个问题,它将无法编译(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 | | }
   | |_^
Run Code Online (Sandbox Code Playgroud)

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

我的第三次尝试旨在构建原始输入的自有版本,从而避免了具有不同生命周期边界的反序列化对象的问题.这个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);
}
Run Code Online (Sandbox Code Playgroud)

这使得该函数现在适用于纯字符串切片,但不适用于包含它们的复合对象(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]
Run Code Online (Sandbox Code Playgroud)

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

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`
Run Code Online (Sandbox Code Playgroud)

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

Fra*_*gné 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]
}
Run Code Online (Sandbox Code Playgroud)

这里的问题是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");
}
Run Code Online (Sandbox Code Playgroud)