Using serde with trait objects

Viewed 1040

I am trying to use serde to serialize trait objects. I had a look at

but I do not understand how to put the pieces together. Please show me how to proceed with this.


This code mimics the example in The Rust Programming Language:

use serde::Serialize; // 1.0.126

trait Paint: Serialize {
    fn paint(&self) -> String;
}

#[derive(Serialize)]
struct Pen {
    color: String,
}

#[derive(Serialize)]
struct Brush {
    color: String,
}

impl Paint for Pen {
    fn paint(&self) -> String {
        return format!("Pen painting with color {}", self.color);
    }
}

impl Paint for Brush {
    fn paint(&self) -> String {
        return format!("Brush painting with color {}", self.color);
    }
}

#[derive(Serialize)]
struct Canvas {
    height: f32,
    width: f32,
    tools: Vec<Box<dyn Paint>>,
}

impl Paint for Canvas {
    fn paint(&self) -> String {
        let mut s = String::new();
        for tool in &self.tools {
            s.push_str("\n");
            s.push_str(&tool.paint());
        }

        return s;
    }
}

fn main() {
    let pen = Pen {
        color: "red".to_owned(),
    };
    let brush = Brush {
        color: "blue".to_owned(),
    };
    let canvas = Canvas {
        height: 12.0,
        width: 10.0,
        tools: vec![Box::new(pen), Box::new(brush)],
    };

    println!("{}", canvas.paint());
    serde_json::to_string(&canvas).unwrap();
}

The code does not compile due to the object-safety rules:

error[E0038]: the trait `Paint` cannot be made into an object
   --> src/main.rs:33:12
    |
33  |     tools: Vec<Box<dyn Paint>>,
    |            ^^^^^^^^^^^^^^^^^^^ `Paint` cannot be made into an object
    |
    = help: consider moving `serialize` to another trait
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
   --> /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.126/src/ser/mod.rs:247:8
    |
247 |     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    |        ^^^^^^^^^ ...because method `serialize` has generic type parameters
    | 
   ::: src/main.rs:3:7
    |
3   | trait Paint: Serialize {
    |       ----- this trait cannot be made into an object...

I understand that trait objects cannot have methods with generic parameters, but in my case I only need to serialize the Canvas struct to JSON. Is there something I could do to make that work?

The ideal serialized output would be

{
    "Canvas": {
        "height": 12.0,
        "width": 10.0,
        "tools": {
            "Pen": {
                "color": "red"
            },
            "Brush": {
                "color": "blue"
            }
        }
    }
}
0 Answers
Related