Method that accepts different values that all implement same trait

I'd like to create a method that accepts a Vec of values that all implement serde::ser::Serializable. (The method iterates over the vec, serializes each value to bytes, and writes the bytes to disk).

Is there any way to do this? In Java/Typescript/etc. there might be an interface that all the objects could implement.

Normally, I would create a trait with a method called to_bytes or something like that, but in this case I want to directly pass each value, x to rmp_serde::encode::write_named(&mut writer, x) b/c I want to serialize the values directly to the file (avoid intermediate allocations / copies).

I tried making the parameter vals: Vec<Box<dyn serde::ser::Serialize>>, but that didn't work.

Isn't serde::ser::Serialize itself the interface (trait, in Rust terms) you need?

If the method has the generic bound T: serde::ser::Serialize and the parameter is: values: Vec<T>, this won't work since Vecs can only store values of a single type.

@Cerberuser

You'll probably need the erased-serde crate.

1 Like

This is because serde::Serialize is not object-safe; check out dtolnay's erased-serde crate.

3 Likes

FWIW, and this is kind of what erased-serde will end up doing, anyways, you can offer a dyn-safe "frontend" for the crate.

  1. Your objective:

  2. From there, the key function is https://docs.rs/rmp-serde/0.15.4/rmp_serde/encode/fn.write_named.html:

    fn write_named<W: ?Sized, T: ?Sized>(
        wr: &mut W, 
        val: &T
    ) -> Result<(), Error> 
    where
        W: Write,
        T: Serialize, // <- we target this trait, so this shall be Self
    

    becoming the trait's method (val -> self, T -> Self):

    //                  frontend trait
    //                  vvvvvvvvvvvvv
    impl<T : Serialize> RmpWriteNamed for T {
        fn write_named<W: ?Sized>(
            self: &Self
            wr: &mut W, 
        ) -> Result<(), Error> 
        where
            W: Write,
        {
            …
        }
    }
    
  3. Our objective now is to make it object-safe: it currently isn't yet since remains that
    &mut impl ?Sized + Write generic parameter. But at this point, it's easy: just replace it with a &mut dyn Write parameter:

    fn write_named (
        self: &Self,
        wr: &mut dyn Write,
    ) -> Result<(), Error>
    

Hence,

The solution

trait RmpWriteNamed {
    fn write_named (
        self: &Self,
        wr: &mut dyn Write,
    ) -> Result<(), Error>
    ;
}

impl<T : Serialize> RmpWriteNamed for T {
    fn write_named (
        self: &Self,
        wr: &mut dyn Write,
    ) -> Result<(), Error>
    {
        ::rmp_serde::encode::write_named(wr, self)
    }
}

And now you can use dyn RmpWriteNamed and its .write_named(writer) method :slightly_smiling_face:

1 Like