Serde: No way to use in dynamic dispatch scenarios.

Created on 14 Jun 2016  路  9Comments  路  Source: serde-rs/serde

I might be wrong about anything below, and I would appreciate workarounds/fixes.

I'm porting a Go logging library to Rust. One of the futures is pluggable, run-time swapable "drains" - destination for log messages (records). A drain could be: a json file, or syslog CSV-formated lines etc. Usually it's just an io (like file) and method of writting to it (serializer). And combiners / filters, that wrap one or more drains, and present them as a one drain.

As the drains for loggers are swappable at runtime etc. they need to handled as Box<Drain> where Drain is the trait that any drain implementation can implement. So they need to be trait objects.

The user should be able to provide any values that are serializable, and they would be carried to the current drain to be formatted and written to disk.

Currently it's not possible with neither rust-serialize or serde. The Box<Drain> would have to either somehow return a trait object of type serde::Serializer, or something that would take serde::Serialize. Neither of these two trait objects can be used. Serialize has a method parameterized over S : Serializer, and Serializer is parametrized over Error which prevents taking any argument of type Box<Serializer> or Box<Serialize>.

So it looks like serialization is completely unusable for dynamic dispatch cases, which although rare, are sometimes necessary.

Problem would be solvable if Serializer would not be parametrized over Error, and eg. some enum would be used, or a struct like std::io::Error.

enhancement

Most helpful comment

They're just different traits where the generic methods in serde::Serialize and serde::Serializer have been replaced with analogous methods that are not generic and don't rely on associated types. Some examples:

// Before
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer;
fn serialize_some<T>(&mut self, value: T) -> Result<(), Self::Error> where T: Serializer;
fn serialize_seq(&mut self, len: Option<usize>) -> Result<Self::SeqState, Self::Error>;

// After
fn erased_serialize(&self, serializer: &mut Serializer) -> Result<(), Error>;
fn erased_serialize_some(&mut self, value: &Serialize) -> Result<(), Error>;
fn erased_serialize_seq(&mut self, len: Option<usize>) -> Result<SeqState, Error>;

Notice that type parameters <S> and <T> have been replaced by trait objects, and associated types like Self::SeqState and Self::Error have been replaced by concrete types. The concrete SeqState is conceptually equivalent to Box<Any> (a little more complicated but only for technical reasons).

The "magic" is in the four impls provided by the erased_serde crate:

impl<T: ?Sized> erased_serde::Serialize for T where T: serde::Serialize;
impl<'a> serde::Serialize for &'a erased_serde::Serialize;
impl<T: ?Sized> erased_serde::Serializer for T where T: serde::Serializer;
impl<'a> serde::Serializer for &'a mut erased_serde::Serializer;

These four impls (with super straightforward implementations, see src/ser.rs) mean that the erased_serde trait objects are usable as Serde types and all Serde types are usable as erased_serde trait objects.

All 9 comments

Uh-oh, we just added even more associated types to Serializer in #437.

@dpc I noticed that serde support has been added to slog since you opened this issue. Have you managed to solve this in a way that is satisfactory to you?

Yeah, I'm fine with that. I don't think this is a serde-specific issue. It's just how Rust works, and it makes sense. I've seen some approaches to work around it, and I actually found satisfying solution for slog already. Closing.

@dpc or anyone else looking for this: you may be interested in erased-serde which provides Serialize and Serializer traits that can be used as trait objects like &Serializer or boxed trait objects like Box<Serializer>. They work with any existing Serde Serialize or Serializer. See this example.

Wow. Awesome. Can you point to the place in the code that is actually doing the "magic"? I'm wondering what's the approach so I could reuse it in the future.

They're just different traits where the generic methods in serde::Serialize and serde::Serializer have been replaced with analogous methods that are not generic and don't rely on associated types. Some examples:

// Before
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer;
fn serialize_some<T>(&mut self, value: T) -> Result<(), Self::Error> where T: Serializer;
fn serialize_seq(&mut self, len: Option<usize>) -> Result<Self::SeqState, Self::Error>;

// After
fn erased_serialize(&self, serializer: &mut Serializer) -> Result<(), Error>;
fn erased_serialize_some(&mut self, value: &Serialize) -> Result<(), Error>;
fn erased_serialize_seq(&mut self, len: Option<usize>) -> Result<SeqState, Error>;

Notice that type parameters <S> and <T> have been replaced by trait objects, and associated types like Self::SeqState and Self::Error have been replaced by concrete types. The concrete SeqState is conceptually equivalent to Box<Any> (a little more complicated but only for technical reasons).

The "magic" is in the four impls provided by the erased_serde crate:

impl<T: ?Sized> erased_serde::Serialize for T where T: serde::Serialize;
impl<'a> serde::Serialize for &'a erased_serde::Serialize;
impl<T: ?Sized> erased_serde::Serializer for T where T: serde::Serializer;
impl<'a> serde::Serializer for &'a mut erased_serde::Serializer;

These four impls (with super straightforward implementations, see src/ser.rs) mean that the erased_serde trait objects are usable as Serde types and all Serde types are usable as erased_serde trait objects.

This is the broader set of impls I arrived at for the latest release:

impl<T: ?Sized> erased_serde::Serialize for T where T: serde::Serialize;
impl<'a> serde::Serialize for erased_serde::Serialize + 'a;
impl<'a> serde::Serialize for erased_serde::Serialize + 'a + Send;
impl<'a> serde::Serialize for erased_serde::Serialize + 'a + Sync;
impl<'a> serde::Serialize for erased_serde::Serialize + 'a + Send + Sync;
impl<T: ?Sized> erased_serde::Serializer for T where T: serde::Serializer;
impl<'a> serde::Serializer for &'a mut erased_serde::Serializer;
impl<'a> serde::Serializer for &'a mut (erased_serde::Serializer + Send);
impl<'a> serde::Serializer for &'a mut (erased_serde::Serializer + Sync);
impl<'a> serde::Serializer for &'a mut (erased_serde::Serializer + Send + Sync);
impl serde::Serializer for Box<erased_serde::Serializer>;
impl serde::Serializer for Box<erased_serde::Serializer + Send>;
impl serde::Serializer for Box<erased_serde::Serializer + Sync>;
impl serde::Serializer for Box<erased_serde::Serializer + Send + Sync>;

Why do you need to repeat 4 different combinations of everything? Aren't one without any additional constrains accepting all the others?

Unfortunately not:

trait Serialize {}
trait ErasedSerialize {}

impl<T: ?Sized + Serialize> Serialize for Box<T> {}

impl<'a> Serialize for ErasedSerialize + 'a {}

fn assert<T: ?Sized + Serialize>() {}

fn main() {
    assert::<ErasedSerialize>(); // ok
    assert::<Box<ErasedSerialize>>(); // ok
    assert::<ErasedSerialize + Send>(); // fails
    assert::<Box<ErasedSerialize + Send>>(); // fails
}

And there is no way to impl<T> Trait1 for Trait2 + T or impl<T> Trait1 for T + Send.

The repetitive impls are defined by a macro here and here so it isn't so bad.

impl_serialize_for_trait_object!(Serialize);
impl_serialize_for_trait_object!(Serialize + Send);
impl_serialize_for_trait_object!(Serialize + Sync);
impl_serialize_for_trait_object!(Serialize + Send + Sync);

impl_serializer_for_trait_object!({'a} &'a mut Serializer);
impl_serializer_for_trait_object!({'a} &'a mut (Serializer + Send));
impl_serializer_for_trait_object!({'a} &'a mut (Serializer + Sync));
impl_serializer_for_trait_object!({'a} &'a mut (Serializer + Send + Sync));
impl_serializer_for_trait_object!({} Box<Serializer>);
impl_serializer_for_trait_object!({} Box<Serializer + Send>);
impl_serializer_for_trait_object!({} Box<Serializer + Sync>);
impl_serializer_for_trait_object!({} Box<Serializer + Send + Sync>);
Was this page helpful?
0 / 5 - 0 ratings