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


person E_net4 the curator    schedule 01.10.2017    source источник
comment
Арг, я просто пытался сделать именно это.   -  person detly    schedule 04.11.2020


Ответы (3)


К сожалению, вам нужна функция, которая еще не реализована в 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 + for<'b> PartialEq<Self::Member<'b>> + Serialize + Deserialize<'a>;
}

struct I32Family;
struct StrFamily;

impl SerdeFamily for I32Family {
    type Member<'a> = i32; // ignoring a parameter is allowed
}

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");
}
person Francis Gagné    schedule 01.10.2017
comment
Возможно, стоит пересмотреть этот ответ, теперь, когда у нас, предположительно, достаточно GAT, реализованных в ночное время для этого. К сожалению, данный код не работает даже после нескольких настроек. Это было лучшее, что я мог пока сделать. не компилируется. - person E_net4 the curator; 08.02.2021

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

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

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

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

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

После того, как мы реализуем необходимые черты на _25 _ / _ 26_ и адаптируем 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's more!", 36));
person E_net4 the curator    schedule 01.10.2017

Простое (но немного неудобное) решение: предоставить 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);
}
person Yohei    schedule 19.01.2021