Как мы можем написать общую функцию для проверки сериализации Sered и десериализации?

В проекте, в котором задействованы стандартные методы сериализации и десериализации 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);
}

Это хорошо работает для владения типами, но не для типов с ограничениями продолжительности жизни (Игровая площадка):

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]

Эта реализация приводит к другой проблеме, и она не будет компилироваться (Игровая площадка).

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 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 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 (стабильная или ночная), и если да, то какие корректировки моей реализации отсутствуют?

Ответ 1

К сожалению, вам нужна функция, которая еще не реализована в 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");
}

Ответ 2

Ответ от Francis Gagné показал, что мы не можем сделать это эффективно без общих типов. Установление глубокого владения десериализованным объектом - это возможная работа, о которой я расскажу здесь.

Третья попытка очень близка к гибкому решению, но она не подходит из-за того, как работает std::borrow::ToOwned. Эта черта не подходит для получения глубоко принадлежащей версии объекта. Например, попытка использования реализации ToOwned для &str дает вам еще один срез строки.

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

Аналогично, тип Owned для структуры, содержащей строковые срезы, не может быть структурой, содержащей String s. В коде:

#[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 для использования нового признака, код теперь компилируется и успешно выполняется (площадка):

#[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 more!", 36));