Why does this program doesn't compile? And how to fix this? I am trying to serialize a generic enum with serde, where the generic type is serializable.
use serde::{Deserialize, Serialize};
pub trait Row: for<'de> Deserialize<'de> + Serialize + Clone + Send + PartialEq {
fn key(&self) -> Option<u64> {
None
}
fn timestamp(&self) -> Option<u64> {
None
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize)]
pub enum MessageContent<R: Row> {
// Other serializable members
Data(R),
}
fn main() {
// Create object of MessageContent and serialize.
}
The program doesn't compile due to a redundant trait bound resulting from the Deserialize
derive macro expansion.
Consider the much simpler program:
use serde::Deserialize;
pub trait Row: for<'de> Deserialize<'de> {}
#[derive(Deserialize)]
pub struct MessageContent<R: Row>(R);
If we cargo expand
the macros, we will see that R
has a Row
bound and a serde::Deserialize<'de>
bound (in the where
clause):
#[automatically_derived]
impl<'de, R: Row> _serde::Deserialize<'de> for MessageContent<R> where
R: _serde::Deserialize<'de> { ... }
If we try to compile it, an error hints that this seemingly-harmless redundancy is disallowed:
error[E0283]: type annotations needed: cannot satisfy `R: Deserialize<'_>`
note: multiple `impl`s or `where` clauses satisfying `R: Deserialize<'_>` found
To resolve this, we must remove one of the redundant bounds. To keep your Row
trait and MessageContent
enum the same, we can remove the Deserialize
macro's extra bound. We use the #[serde(bound(...))]
attribute to tell the macro that Raw
is the bound that makes R
deserializable. Note that for<'de> Deserialize<'de>
is better written as serde::de::DeserializeOwned
.
use serde::{de::DeserializeOwned, Deserialize, Serialize};
pub trait Row: DeserializeOwned + Serialize + Clone + Send + PartialEq {
fn key(&self) -> Option<u64> { None }
fn timestamp(&self) -> Option<u64> { None }
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize)]
pub enum MessageContent<R: Row> {
#[serde(bound(deserialize = "R: Row"))]
Data(R),
}
Here's what the new cargo expand
output looks like:
#[automatically_derived]
impl<'de, R: Row> _serde::Deserialize<'de> for MessageContent<R> where
R: Row { ... }
The bounds match, which apparently satisfies the Rust compiler.
Finally, just so you know, placing trait bounds on struct
and enum
generic parameters is not only discouraged but one of the causes of this issue.
This version also works, and is arguably more idiomatic. It just wouldn't allow you to use <R as Row>::SomeAssociatedType
within the enum
:
use serde::{de::DeserializeOwned, Deserialize, Serialize};
pub trait Row: DeserializeOwned + Serialize + Clone + Send + PartialEq {
type SomeAssociatedType = u8;
fn key(&self) -> Option<u64> { None }
fn timestamp(&self) -> Option<u64> { None }
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize)]
pub enum MessageContent<R> {
Data(R),
// Couldn't do this: Data2(<R as Row>::SomeAssociatedType)
}