19

I'm trying to solve the problem of serializing and deserializing Box<SomeTrait>. I know that in the case of a closed type hierarchy, the recommended way is to use an enum and there are no issues with their serialization, but in my case using enums is an inappropriate solution.

At first I tried to use Serde as it is the de-facto Rust serialization mechanism. Serde is capable of serializing Box<X> but not in the case when X is a trait. The Serialize trait can’t be implemented for trait objects because it has generic methods. This particular issue can be solved by using erased-serde so serialization of Box<SomeTrait> can work.

The main problem is deserialization. To deserialize polymorphic type you need to have some type marker in serialized data. This marker should be deserialized first and after that used to dynamically get the function that will return Box<SomeTrait>.

std::any::TypeId could be used as a marker type, but the main problem is how to dynamically get the deserialization function. I do not consider the option of registering a function for each polymorphic type that should be called manually during application initialization.

I know two possible ways to do it:

  1. Languages that have runtime reflection like C# can use it to get deserialization method.
  2. In C++, the cereal library uses magic of static objects to register deserializer in a static map at the library initialization time.

But neither of these options is available in Rust. How can deserialization of polymorphic objects be added in Rust if at all?

4
  • 1
    "But in my case using enums is inappropriate solution." Can we know why? It sounds like the whole issue would fade away when using an enum. Note that type erasure in erased_serde happens at the Deserializer, not on the object being deserialized. Commented May 28, 2017 at 21:20
  • 1
    "Can we know why?" I'm trying to have minimal number of dependencies. I have a trait in some very small and very basic module and a lot of code that depends on it. And I want to have each of implementations of this trait in a separate module. Also there is another downside of using Commented May 29, 2017 at 5:13
  • 1
    Sorry, I accidentally posted comment before it was finished. Also there is another downside of using enums: if you have the same method in all enum subtypes to call it on enum you have to add match construction with similar cases for each type. "type erasure in erased_serde happens at the Deserializer, not on the object being deserialized" Yes, I know that. I mentioned in the question that erased_serder doesn't solve problem with deserialization, only with serialization. Commented May 29, 2017 at 5:23
  • You may be interested in the Rust package enum_dispatch, which may solve your program from the other direction. It provides a derive macro that generates the boilerplate match expression to implement traits for the enum automatically. Commented Aug 19, 2019 at 13:10

3 Answers 3

11

This has been implemented by dtolnay.

The concept is quite clever ans is explained in the README:

How does it work?

We use the inventory crate to produce a registry of impls of your trait, which is built on the ctor crate to hook up initialization functions that insert into the registry. The first Box<dyn Trait> deserialization will perform the work of iterating the registry and building a map of tags to deserialization functions. Subsequent deserializations find the right deserialization function in that map. The erased-serde crate is also involved, to do this all in a way that does not break object safety.

To summarize, every implementation of the trait declared as [de]serializable is registered at compile-time, and this is resolved at runtime in case of [de]serialization of a trait object.

Sign up to request clarification or add additional context in comments.

2 Comments

it does not solve deserializations for me with the following error: error: deserialization of generic traits is not supported yet; use #[typetag::serialize] to generate serialization only Does that worked for you ?
@curious I'm really sorry, I haven't used that feature for a while, and I don't have any time at all to look at it. You should either ask in a community chat, or open another question here, if you dare go for it.
-1

All your libraries could provide a registration routine, guarded by std::sync::Once, that register some identifier into a common static mut, but obviously your program must call them all.

I've no idea if TypeId yields consistent values across recompiles with different dependencies.

Comments

-1

A library to do this should be possible. To create such a library, we would create a bidirectional mapping from TypeId to type name before using the library, and then use that for serialization/deserialization with a type marker. It would be possible to have a function for registering types that are not owned by your package, and to provide a macro annotation that automatically does this for types declared in your package.

If there's a way to access a type ID in a macro, that would be a good way to instrument the mapping between TypeId and type name at compile time rather than runtime.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.