rusttypesserde

type annotations needed: cannot satisfy `T: Deserialize<'_>`


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.
}

Solution

  • 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)
    }