【问题标题】:How do I deserialize into trait, not a concrete type?我如何反序列化为特征,而不是具体类型?
【发布时间】:2017-02-22 13:32:41
【问题描述】:

我正在尝试进行结构序列化,其中字节最终将通过管道发送、重构并在其上调用方法。

我创建了一个特征,这些结构将根据需要实现,我正在使用 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();
}

当我尝试使用 trait 作为类型来重构字节时(假设我不知道正在发送哪个底层对象),编译器会抱怨该 trait 没有实现 Sized trait:

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[..]);

有没有更好的模式来实现这种序列化+多态行为?

【问题讨论】:

  • 我……不认为你能做到。除非您知道它的具体类型,否则您无法恢复该结构,并且除非您有指向其 vtable 的指针,否则您无法在其上调用方法——除非您有权访问其具体类型,否则您无法弄清楚。你能序列化一个 vtable 吗?
  • 似乎是这样,但我希望有人能指出我遗漏的东西。我对此有一个非惯用的解决方案,但增加了对代码的耦合......所以我正在寻找更好的东西。
  • 你确定你想要多态而不是简单的枚举吗?你需要你的代码来处理用户提供的类型吗?
  • 我……你知道……但是……不。你是对的,@ker。当使用带有关联数据的枚举时,我所拥有的“非惯用”解决方案变得更加自然。我一直尝试将枚举用作标准 C 枚举,但我可以更改我的设计以使用枚举。如果您将您的建议作为答案发布,我会接受。
  • 反序列化成一个还为所有其他Into 实现Contract 实现的实现怎么样?

标签: serialization rust serde cbor


【解决方案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)
            }
        }
    }
}

【讨论】:

  • 使用结构 Bar 和 Baz 作为枚举的关联数据,但在其他方面与此设计非常吻合。谢谢!
  • 如果有来自带有类型参数的 trait 的任意类型集怎么办?
  • @Shisoft 不确定我是否理解。你为什么不打开一个新问题?
【解决方案2】:

您可以使用typetag 来解决问题。将#[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"

另见:

【讨论】:

【解决方案3】:

添加到oli_obk's answer,可以使用Serde的enum representation来区分类型。

在这里,我使用内部标记表示将这两个相似的对象反序列化为适当的变体:

{
  "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,
}

【讨论】:

  • 如何从第二个示例中“解压”结构?目前输出为File(File{path:"foo"})。获取File{path:"foo"} 的最干净的实现是什么?
猜你喜欢
  • 2014-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-17
  • 2021-08-15
  • 1970-01-01
相关资源
最近更新 更多