Serde for trait objects - Part 2: Serialization
In this series of blog posts I’m explaining how to use serde with trait objects:
- Part 1: Overview
- Part 2: Serialization
- Part 3: Deserialization
- Part 4: Registry
- Part 5: Lifetimes
- Part 6: Sync/Send
- Part 7: Macro Part A: Trait
- Part 8: Marco Part B: Implementation
Remark: If you are in a situation where you want to serialize a trait object, please take a step back. Check if you can replace your trait object with an enum. In my experience, the enum approach is much easier to work with.
Remark: All topics covered here are well-known. We follow typetag.
So, let’s start. Today, our quest is to serialize a trait object. This will be a short post.
We start with the following code, which defines a trait and a struct implementing it. We try to serialize a trait object instance:
This compiles and prints:
Last time, we have seen, that we need to enhance the serialized trait object with some information about the underlying type. Note, there are several possibilities to serialize an enum (enum representation). Today, we will use “externally tagged”, which is the default for enums in Serde. 1
Hence, our aim is to get the following output:
First, we need to get some type information. In the final version, the addition of the function to the trait will be part of the macro. 2
Now we want to use this type information during serialization. Note that our json template {type-info: type-serialization} corresponds to a “key-value map” {key: value} in the serde world. (In our example, {“S”: {“data”:0}}, we have the key/type-info “S” and the value/type-serialization {“data”: 0}).
Here is our first try, which compiles, but is recursive - wrong method resolution used.
The problem is that serialize_entry
takes it arguments by reference.
As we saw last time, this leads to a recursive function call.
We can help the compiler by adding a new layer: We introduce a newtype wrapper Wrap
, which in turn calls our working implementation of serialize
.
This gives our final code:
This gives our target output:
So we’re done for today. Thank you for following the blog series.
Next time we will proceed with deserialization. Lifetimes for serialization (and deserialization) will be the content of Part 5.
Footnotes
-
Last time, we used dotnet as motivation, which used “Internally tagged”. This “Internally tagged” variant is my preferred variant for self-describing formats, but a lot more involved to implement in Rust. We will implement this is a later blog post. ↩
-
Since derive macros cannot change the code, only add code, we will need a attribute macro. ↩