Add a `SerializeOwned` trait for owning serialization
TL;DR: Serialize::serialize takes values by reference but a lot of the time they are thrown away after serialization
I really like Rust! Especially its destructive move semantics. However, serde force a pattern for serialization which reminds me a lot of C++ move semantics. Many times have I built up a value just to serialize it to some stream, dropping it right afterwards. However, because the Serialize::serialize method takes the value by reference, any transformations to convert a value from its in-memory representation to one more suitable for storage force users into either cloning excessively or writing a lot of Boilerplate to avoid it.
I think many use cases could profit from destructive serialization.
My suggestion is a new SerializeOwned trait, such that &T: SerializeOwned where T: Serialize.
Now, the Serializer trait could add a new method serialize_owned, with a default implementation that simply panics on use (ala unimplemented!()). Implementations could then move over to the new trait and if serde ever has a major version bump, just remove the default impl and instead provide it for serialize.
This would make writing custom serialization a lot less of a pain in these cases.
I hope this sounds somewhat understandable 😅
Honestly, it's not very clear why passing a reference makes you clone. Not vice versa? An example would be helpful.
I have a program which starts by loading an object from a file, does some operations on it, then writes it back to the file.
Its data model looks roughly like some:
#[derive(Serialize)]
struct Foo { name: String }
struct Group { foos_by_key: Vec<Key> }
struct Bar {
foos: Map<Key, Foo>,
groups: Vec<Group>
}
I like human-readable files and so I decided that I wanted to refer to the Foos by name in the serialization of Bar::groups, like this:
{
"foos": [
{ "name": "alice" },
{ "name": "bob" }
],
"groups": [
{
"foos_by_name": [ "alice", "bob" ]
}
]
}
This means that while serializing groups I need to look up keys in foos, thus requiring a custom Deserialize impl for some wrapper over a reference to foos and a Group. But now I can't just serialize Bar::groups, because Group is not serializable, only its wrapper is!
There are two approaches:
- iterate over the the
Vec<Group>, transform it to the desired, thencollectit into anotherVec. This is effectively an unnecessary clone of theVec. - use an
Iteratorfor serialization.
I went with the second option, because it can be made generic over the iterator, which helps, because this exact problem happens already twice in the above example.
However, a &mut is required to drive an Iterator, which makes sense, but does not easily work with serialization. In theory, this can be sidestepped by interior mutability, but I wasn't sure whether this is even fine. It should be, but it somewhat breaks the contract of Serialize, because it breaks the contract that serialization is idempotent. Instead I made a wrapper around an Iterator, which clones it beforehand, so that it may be used. For the specific case of Iterators, this kinda already exists as the Serializer::collect_* methods, but it still requires a wrapper type which clones the iterator.
This might be a contrived example, but assume I wanted to serialize a File by contents. I could first std::fs::read_to_string, then serialize that and finally drop the string buffer used to hold the contents, but it feels icky to me to immediately drop a heap allocated buffer just to pass a reference.
It might also allow serialization of std::sync::Exclusive and similar types (e.g. bevy's SyncCell).