Why do I get an UnsupportedType error when serializing to TOML with a manually implemented Serialize for an enum with struct variants? - serialization

I'm trying to implement Serialize for an enum that includes struct variants. The serde.rs documentation indicates the following:
enum E {
// Use three-step process:
// 1. serialize_struct_variant
// 2. serialize_field
// 3. end
Color { r: u8, g: u8, b: u8 },
// Use three-step process:
// 1. serialize_tuple_variant
// 2. serialize_field
// 3. end
Point2D(f64, f64),
// Use serialize_newtype_variant.
Inches(u64),
// Use serialize_unit_variant.
Instance,
}
With that in mind, I proceeded to implemention:
use serde::ser::{Serialize, SerializeStructVariant, Serializer};
use serde_derive::Deserialize;
#[derive(Deserialize)]
enum Variants {
VariantA,
VariantB { k: u32, p: f64 },
}
impl Serialize for Variants {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
Variants::VariantA => serializer.serialize_unit_variant("Variants", 0, "VariantA"),
Variants::VariantB { ref k, ref p } => {
let mut state =
serializer.serialize_struct_variant("Variants", 1, "VariantB", 2)?;
state.serialize_field("k", k)?;
state.serialize_field("p", p)?;
state.end()
}
}
}
}
fn main() {
let x = Variants::VariantB { k: 5, p: 5.0 };
let toml_str = toml::to_string(&x).unwrap();
println!("{}", toml_str);
}
The code compiles, but when I run it it fails:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: UnsupportedType', src/libcore/result.rs:999:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
I figured the issue must be in my use of the API, so I consulted the API documentation for StructVariant and it looks practically the same as my code. I'm sure I'm missing something, but I don't see it based on the docs and output.

Enabling external tagging for the enum enables Serde to serialize/deserialize it to TOML:
#[derive(Deserialize)]
#[serde(tag = "type")]
enum Variants {
VariantA,
VariantB { k: u32, p: f64 },
}
toml::to_string(&Variants::VariantB { k: 42, p: 13.37 })
serializes to
type = VariantB
k = 42
p = 13.37
This works well in Vecs and HashMaps, too.

The TOML format does not support enums with values:
use serde::Serialize; // 1.0.99
use toml; // 0.5.3
#[derive(Serialize)]
enum A {
B(i32),
}
fn main() {
match toml::to_string(&A::B(42)) {
Ok(s) => println!("{}", s),
Err(e) => eprintln!("Error: {}", e),
}
}
Error: unsupported Rust type
It's unclear what you'd like your data structure to map to as TOML. Using JSON works just fine:
use serde::Serialize; // 1.0.99
use serde_json; // 1.0.40
#[derive(Serialize)]
enum Variants {
VariantA,
VariantB { k: u32, p: f64 },
}
fn main() {
match serde_json::to_string(&Variants::VariantB { k: 42, p: 42.42 }) {
Ok(s) => println!("{}", s),
Err(e) => eprintln!("Error: {}", e),
}
}
{"VariantB":{"k":42,"p":42.42}}

Related

Comparing Structs with floating point numbers in rust

My tests fail when using floating point numbers f64 due to precision errors.
Playground:
use std::ops::Sub;
#[derive(Debug, PartialEq, Clone, Copy)]
struct Audio {
amp: f64,
}
impl Sub for Audio {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Self {
amp: self.amp - other.amp,
}
}
}
#[test]
fn subtract_audio() {
let audio1 = Audio { amp: 0.9 };
let audio2 = Audio { amp: 0.3 };
assert_eq!(audio1 - audio2, Audio { amp: 0.6 });
assert_ne!(audio1 - audio2, Audio { amp: 1.2 });
assert_ne!(audio1 - audio2, Audio { amp: 0.3 });
}
I get the following error:
---- subtract_audio stdout ----
thread 'subtract_audio' panicked at 'assertion failed: `(left == right)`
left: `Audio { amp: 0.6000000000000001 }`,
right: `Audio { amp: 0.6 }`', src/lib.rs:23:5
How to test for structs with floating numbers like f64 ?
If the comparing were to be done with numbers without struct,
let a: f64 = 0.9;
let b: f64 = 0.6;
assert!(a - b < f64:EPSILON);
But with structs we need to take extra measures.
First need to derive with PartialOrd to allow comparing with other structs.
#[derive(Debug, PartialEq, PartialOrd)]
struct Audio {...}
next create a struct for comparison
let audio_epsilon = Audio { amp: f64:EPSILON };
now I can compare regularly (with assert! not assert_eq!)
assert!(c - d < audio_epsilon)
An other solution is to implement PartialEq manually:
impl PartialEq for Audio {
fn eq(&self, other: &Self) -> bool {
(self.amp - other.amp).abs() < f64::EPSILON
}
}

Rust Snafu Missing 'source' field

I'm trying to use the snafu crate for error handling, but keep getting erros that my Error enum struct is missing the 'source' and that IntoError is not implimented for Error:
//main.rs
use snafu::{ResultExt, Snafu};
#[derive(Debug, Snafu)]
#[snafu(visibility = "pub(crate)")]
pub enum Error{
#[snafu(display("Could not load gallery JSON: {}: {}", json_path, source))]
LoadGallery {
source: std::io::Error,
json_path: String,
},
}
//gallery.rs
use snafu::{ResultExt};
use crate::Error::{LoadGallery};
pub struct Gallery{
name: String,
}
impl Gallery{
pub fn from_json(json_path: String)->Result<()>{
let configuration = std::fs::read_to_string(&json_path).context(LoadGallery { json_path })?;
Ok(())
}
}
results in:
let configuration = std::fs::read_to_string(&json_path).context(LoadGallery { json_path })?;
| ^^^^^^^^^^^ missing `source`
let configuration = std::fs::read_to_string(&json_path).context(LoadGallery { json_path })?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoError<_>` is not implemented for `Error`
Based on this example from the docs, I don't see what I'm doing wrong:
use snafu::{ResultExt, Snafu};
use std::{fs, io, path::PathBuf};
#[derive(Debug, Snafu)]
enum Error {
#[snafu(display("Unable to read configuration from {}: {}", path.display(), source))]
ReadConfiguration { source: io::Error, path: PathBuf },
#[snafu(display("Unable to write result to {}: {}", path.display(), source))]
WriteResult { source: io::Error, path: PathBuf },
}
type Result<T, E = Error> = std::result::Result<T, E>;
fn process_data() -> Result<()> {
let path = "config.toml";
let configuration = fs::read_to_string(path).context(ReadConfiguration { path })?;
let path = unpack_config(&configuration);
fs::write(&path, b"My complex calculation").context(WriteResult { path })?;
Ok(())
}
fn unpack_config(data: &str) -> &str {
"/some/path/that/does/not/exist"
}
It's because when you're constructing LoadGallery, you're attempting to construct Error::LoadGallery. You then get a compile error saying "missing source", because the Error::LoadGallery variant has a source field. Fixing it is straight forward, you just need to change which LoadGallery you import.
// Not this one:
// use crate::Error::LoadGallery;
// This one:
use crate::LoadGallery;
Why? Because snafu generates a struct for each of Error's variants. So there's a struct LoadGallery being generated. This struct doesn't contain the source field, which is why you can construct it without source and pass it to context(), because it's not actually Error::LoadGallery.
Your from_json() also needs to return Result<(), Error> instead of Result<()> (you don't have the type alias, like in the example.)
use crate::{Error, LoadGallery};
use snafu::ResultExt;
pub struct Gallery {
name: String,
}
impl Gallery {
pub fn from_json(json_path: String) -> Result<(), Error> {
let configuration =
std::fs::read_to_string(&json_path).context(LoadGallery { json_path })?;
Ok(())
}
}
If you're curious you can use cargo expand to inspect what macros expand to. You first need to install it by doing cargo install expand. Then you can execute cargo expand in any project.

How to include the file path in an IO error in Rust?

In this minimalist program, I'd like the file_size function to include the path /not/there in the Err so it can be displayed in the main function:
use std::fs::metadata;
use std::io;
use std::path::Path;
use std::path::PathBuf;
fn file_size(path: &Path) -> io::Result<u64> {
Ok(metadata(path)?.len())
}
fn main() {
if let Err(err) = file_size(&PathBuf::from("/not/there")) {
eprintln!("{}", err);
}
}
You must define your own error type in order to wrap this additional data.
Personally, I like to use the custom_error crate for that, as it's especially convenient for dealing with several types. In your case it might look like this:
use custom_error::custom_error;
use std::fs::metadata;
use std::io;
use std::path::{Path, PathBuf};
use std::result::Result;
custom_error! {ProgramError
Io {
source: io::Error,
path: PathBuf
} = #{format!("{path}: {source}", source=source, path=path.display())},
}
fn file_size(path: &Path) -> Result<u64, ProgramError> {
metadata(path)
.map(|md| md.len())
.map_err(|e| ProgramError::Io {
source: e,
path: path.to_path_buf(),
})
}
fn main() {
if let Err(err) = file_size(&PathBuf::from("/not/there")) {
eprintln!("{}", err);
}
}
Output:
/not/there: No such file or directory (os error 2)
While Denys Séguret's answer is correct, I like using my crate SNAFU because it provides the concept of a context. This makes the act of attaching the path (or anything else!) very easy to do:
use snafu::{ResultExt, Snafu}; // 0.2.3
use std::{
fs, io,
path::{Path, PathBuf},
};
#[derive(Debug, Snafu)]
enum ProgramError {
#[snafu(display("Could not get metadata for {}: {}", path.display(), source))]
Metadata { source: io::Error, path: PathBuf },
}
fn file_size(path: impl AsRef<Path>) -> Result<u64, ProgramError> {
let path = path.as_ref();
let md = fs::metadata(&path).context(Metadata { path })?;
Ok(md.len())
}
fn main() {
if let Err(err) = file_size("/not/there") {
eprintln!("{}", err);
}
}

How do I deserialize into trait, not a concrete type?

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?
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)
}
}
}
}
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:
How can deserialization of polymorphic trait objects be added in Rust if at all?
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,
}

How to use `emit_struct` and `emit_struct_field` to serialize a custom struct?

I've read valve's JSON Serialization in Rust, Part 1 and try to run the code in the blogpost. The most complicated part is do a custom serialization for a custom struct.
I update the snippet so it can run on newest Rust nightly:
extern crate rustc_serialize;
use rustc_serialize::{json, Encodable, Encoder};
struct Person {
name: String,
age: usize,
}
impl Encodable for Person {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
match *self {
Person { name: ref p_name, age: ref p_age, } => {
s.emit_struct("Person", 0, |s| {
try!(s.emit_struct_field( "name", 0, |s| p_name.encode(s)));
try!(s.emit_struct_field( "age", 1, |s| p_age.encode(s)));
try!(s.emit_struct_field( "summary", 2, |s| {
(format!("Nice person named {}, {} years of age", p_name, p_age)).encode(s)
}));
Ok(())
})
},
}
}
}
fn main() {
let person = Person {
name: "John Doe".to_string(),
age: 33,
};
println!("{}" , json::encode(&person).unwrap());
}
The output of above is {}, but the correct result should be:
{"age":33,"name":"John Doe","summary":"Nice person named John Doe, 33 years of age"}
I want to know how to use Encodable trait to serialize a custom struct in right way.
Thank you.
It looks like your tutorial is out-of-date. It says
We call emit_struct on our encoder and pass it 3 arguments: the name of the struct, current index and an anonymous function(aka lambda). The name of the struct is not used; current index is not used too.
But the code says
fn emit_struct<F>(&mut self, _: &str, len: usize, f: F) -> EncodeResult<()> where
F: FnOnce(&mut Encoder<'a>) -> EncodeResult<()>,
{
if self.is_emitting_map_key { return Err(EncoderError::BadHashmapKey); }
if len == 0 {
try!(write!(self.writer, "{{}}"));
So the argument has changed from an index to a length, and it's now meaningful. Here's your example working:
extern crate rustc_serialize;
use rustc_serialize::{json, Encodable, Encoder};
struct Person {
name: String,
age: usize,
}
impl Encodable for Person {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
s.emit_struct("Person", 1, |s| {
try!(s.emit_struct_field("name", 0, |s| self.name.encode(s)));
try!(s.emit_struct_field("age", 1, |s| self.age.encode(s)));
try!(s.emit_struct_field("summary", 2, |s| {
let summary = format!("Nice person named {}, {} years of age", self.name, self.age);
summary.encode(s)
}));
Ok(())
})
}
}
fn main() {
let person = Person {
name: "John Doe".to_string(),
age: 33,
};
println!("{}" , json::encode(&person).unwrap());
}
Note that I also removed the crazy gyrations to destructure self and just access the properties directly.
You problem is with the emit_struct(..) call.
The prototype of this function is:
fn emit_struct<F>(&mut self, name: &str, len: usize, f: F)
-> Result<(), Self::Error>
where F: FnOnce(&mut Self) -> Result<(), Self::Error>;
Here, len is the number of fields of your struct. Bu you are setting it to 0, so the JSON dictionary generated has 0 fields.
Changing it to 3 gives this output:
{"name":"John Doe","age":33,"summary":"Nice person named John Doe, 33 years of age"}