我如何反序列化为特征,而不是具体类型?
How do I deserialize into trait, not a concrete type?
我正在尝试进行结构序列化,其中字节最终将通过管道发送、重构并调用它们的方法。
我创建了一个特征,这些结构将适当地实现,我正在使用 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[..]);
有没有更好的模式来实现这种序列化+多态的行为?
看起来你落入了我从 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)
}
}
}
}
添加到 , you can use Serde's 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,
}
您可以使用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"
另请参阅:
我正在尝试进行结构序列化,其中字节最终将通过管道发送、重构并调用它们的方法。
我创建了一个特征,这些结构将适当地实现,我正在使用 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[..]);
有没有更好的模式来实现这种序列化+多态的行为?
看起来你落入了我从 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)
}
}
}
}
添加到
在这里,我使用内部标记表示将这两个相似的对象反序列化为适当的变体:
{
"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,
}
您可以使用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"
另请参阅: