11

I'm trying to do struct serialization, in which the bytes would eventually be sent down a pipe, reconstructed and methods be called on them.

I created a trait these structs would implement as appropriate and I'm using serde and serde-cbor for serialization:

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(); } 

When I try to reconstruct the bytes using the trait as the type (given that I wouldn't know which underlying object is being sent), the compiler complains that the trait does not implement the 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` 

I guess it makes sense since the compiler doesn't know how big the struct is supposed to be and doesn't know how to line up the bytes for it. If I change the line where I deserialize the object to specify the actual struct type, it works:

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

Is there a better pattern to achieve this serialization + polymorphism behavior?

6
  • 2
    I... don't think you can do that. You can't recover the struct unless you know its concrete type, and you can't call methods on it unless you have a pointer to its vtable -- which you can't figure out unless you have access to its concrete type. Can you serialize a vtable? Commented Feb 22, 2017 at 14:00
  • Seems to be the case, but I was hoping someone would point out something I'm missing. I have a non-idiomatic solution for this but adds coupling to the code... so I'm looking for something better. Commented Feb 22, 2017 at 14:09
  • 3
    Are you sure you want polymorphism and not simply an enum? Do you need your code to work with user supplied types? Commented Feb 22, 2017 at 16:12
  • I.... you know... but....no. You are correct, @ker. The "non-idiomatic" solution I had becomes far more natural when using enums with data associated to them. I keep trying to use enums as standard C enums, but I can change my design to use enums. If you post your suggestions as an answer, I'll accept it. Commented Feb 22, 2017 at 17:42
  • 1
    What about deserializing into an implementation that also implements Into for all other Contract implementations? Commented Feb 22, 2017 at 21:45

3 Answers 3

11

It looks like you fell into the same trap that I fell into when I moved from C++ to Rust. Trying to use polymorphism to model a fixed set of variants of a type. Rust's enums (similar to Haskell's enums, and equivalent to Ada's variant record types) are different from classical enums in other languages, because the enum variants can have fields of their own.

I suggest you change your code to

#[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) } } } } 
Sign up to request clarification or add additional context in comments.

3 Comments

Used the structures Bar and Baz as the associated data for the enum, but went pretty much with this design otherwise. Thanks!
What about if there is arbitrary set of type from a trait with type parameters?
@Shisoft not sure I understand. Why don't you open a new question?
9

You can use typetag to solve the problem. Add #[typetag::serde] (or ::deserialize, as shown here) to the trait and each implementation:

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" 

See also:

2 Comments

Thanks for your answer, Shep! Was this inspired by the answer I just received to another of my questions? stackoverflow.com/questions/57560593/…
@Dash83 it was actually due to How can I use serde to deserialize into a hierarchical decentralized configuration?, which I wanted to close as a duplicate of this and How can deserialization of polymorphic trait objects be added in Rust if at all? (which I'm sad that both questions exist to start with). Neither question had an answer showing how to use typetag though.
3

Adding on to oli_obk's answer, you can use Serde's enum representation to distinguish between the types.

Here, I use the internally-tagged representation to deserialize these two similar objects into the appropriate variant:

{ "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() }); } 

You don't have to squash it all into one enum, you can create separate types as well:

#[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, } 

1 Comment

how would you 'unpack' the struct from the second example? at the moment the output would be File(File{path:"foo"}). What's the cleanest implementation to get File{path:"foo"} instead?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.