Как я могу десериализовать в черту, а не конкретный тип?

Я пытаюсь сделать сериализацию структуры, в которой байты будут в конечном итоге отправлены по каналу, восстановлены и методы будут вызваны на них.

Я создал черту, которую эти структуры будут реализовывать соответствующим образом, и я использую serde и serde-cbor для сериализации:

extern crate serde_cbor;
#[macro_use]
extern crate serde_derive;
extern crate serde;

use serde_cbor::ser::*;
use serde_cbor::de::*;

trait Contract {
    fn do_something(&self);
}

#[derive(Debug, Serialize, Deserialize)]
struct Foo {
    x: u32,
    y: u32,
}

#[derive(Debug, Serialize, Deserialize)]
struct Bar {
    data: Vec<Foo>,
}

#[derive(Debug, Serialize, Deserialize)]
struct Baz {
    data: Vec<Foo>,
    tag: String,
}

impl Contract for Bar {
    fn do_something(&self) {
        println!("I'm a Bar and this is my data {:?}", self.data);
    }
}

impl Contract for Baz {
    fn do_something(&self) {
        println!("I'm Baz {} and this is my data {:?}", self.tag, self.data);
    }
}

fn main() {
    let data = Bar { data: vec![Foo { x: 1, y: 2 }, Foo { x: 3, y: 4 }, Foo { x: 7, y: 8 }] };
    data.do_something();

    let value = to_vec(&data).unwrap();
    let res: Result<Contract, _> = from_reader(&value[..]);
    let res = res.unwrap();
    println!("{:?}", res);
    res.do_something();
}

Когда я пытаюсь восстановить байты, используя черту в качестве типа (учитывая, что я не знаю, какой базовый объект отправляется), компилятор жалуется, что черта не реализует Sized черта характера:

error[E0277]: the trait bound `Contract: std::marker::Sized` is not satisfied
  --> src/main.rs:52:15
   |
52 |     let res: Result<Contract, _> = from_reader(&value[..]);
   |              ^^^^^^^^^^^^^^^^^^^ the trait `std::marker::Sized` is not implemented for `Contract`
   |
   = note: `Contract` does not have a constant size known at compile-time
   = note: required by `std::result::Result`

Я думаю, это имеет смысл, поскольку компилятор не знает, насколько большой должна быть структура, и не знает, как выстроить байты для нее. Если я изменю строку, где я десериализую объект, чтобы указать фактический тип структуры, он будет работать:

let res: Result<Bar, _> = from_reader(&value[..]);

Есть ли лучший шаблон для достижения этого поведения сериализации + полиморфизм?

1 ответ

Решение

Похоже, вы попали в ту же ловушку, в которую попал я, когда перешел с C++ на Rust. Попытка использовать полиморфизм для моделирования фиксированного набора вариантов типа. Перечисления Rust (аналогичные перечислениям Haskell и эквивалентные типам записей вариантов Ada) отличаются от классических перечислений в других языках, потому что варианты перечисления могут иметь свои собственные поля.

Я предлагаю вам изменить свой код на

#[derive(Debug, Serialize, Deserialize)]
enum Contract {
    Bar { data: Vec<Foo> },
    Baz { data: Vec<Foo>, tag: String },
}

#[derive(Debug, Serialize, Deserialize)]
struct Foo {
    x: u32,
    y: u32,
}

impl Contract {
    fn do_something(&self) {
        match *self {
            Contract::Bar { ref data } => println!("I'm a Bar and this is my data {:?}", data),
            Contract::Baz { ref data, ref tag } => {
                println!("I'm Baz {} and this is my data {:?}", tag, data)
            }
        }
    }
}

Вы можете использовать теги для решения проблемы. добавлять#[typetag::serde] (или ::deserialize, как показано здесь) к трейту и каждой реализации:

use serde::Deserialize;

#[typetag::deserialize(tag = "driver")]
trait Contract {
    fn do_something(&self);
}

#[derive(Debug, Deserialize, PartialEq)]
struct File {
    path: String,
}

#[typetag::deserialize(name = "file")]
impl Contract for File {
    fn do_something(&self) {
        eprintln!("I'm a File {}", self.path);
    }
}

#[derive(Debug, Deserialize, PartialEq)]
struct Http {
    port: u16,
    endpoint: String,
}

#[typetag::deserialize(name = "http")]
impl Contract for Http {
    fn do_something(&self) {
        eprintln!("I'm an Http {}:{}", self.endpoint, self.port);
    }
}

fn main() {
    let f = r#"
{
  "driver": "file",
  "path": "/var/log/foo"
}
"#;

    let h = r#"
{
  "driver": "http",
  "port": 8080,
  "endpoint": "/api/bar"
}
"#;

    let f: Box<dyn Contract> = serde_json::from_str(f).unwrap();
    f.do_something();

    let h: Box<dyn Contract> = serde_json::from_str(h).unwrap();
    h.do_something();
}
[dependencies]
serde_json = "1.0.57"
serde = { version = "1.0.114", features = ["derive"] }
typetag = "0.1.5"

Смотрите также:

Добавляя к ответу oli_obk, вы можете использовать перечисление Serde, чтобы различать типы.

Здесь я использую внутренне помеченное представление для десериализации этих двух похожих объектов в соответствующий вариант:

{
  "driver": "file",
  "path": "/var/log/foo"
}
{
  "driver": "http",
  "port": 8080,
  "endpoint": "/api/bar"
}
use serde; // 1.0.82
use serde_derive::*; // 1.0.82
use serde_json; // 1.0.33

#[derive(Debug, Deserialize, PartialEq)]
#[serde(tag = "driver")]
enum Driver {
    #[serde(rename = "file")]
    File { path: String },
    #[serde(rename = "http")]
    Http { port: u16, endpoint: String }
}

fn main() {
    let f = r#"   
{
  "driver": "file",
  "path": "/var/log/foo"
}
"#;

    let h = r#"
{
  "driver": "http",
  "port": 8080,
  "endpoint": "/api/bar"
}
"#;

    let f: Driver = serde_json::from_str(f).unwrap();
    assert_eq!(f, Driver::File { path: "/var/log/foo".into() });

    let h: Driver = serde_json::from_str(h).unwrap();
    assert_eq!(h, Driver::Http { port: 8080, endpoint: "/api/bar".into() });
}

Вам не нужно объединять все это в одно перечисление, вы также можете создавать отдельные типы:

#[derive(Debug, Deserialize, PartialEq)]
#[serde(tag = "driver")]
enum Driver {
    #[serde(rename = "file")]
    File(File),
    #[serde(rename = "http")]
    Http(Http),
}

#[derive(Debug, Deserialize, PartialEq)]
struct File {
    path: String,
}

#[derive(Debug, Deserialize, PartialEq)]
struct Http {
    port: u16,
    endpoint: String,
}
Другие вопросы по тегам