Serde Deserializer Visitor Error: Expected Type Parameter E

I have a pretty simple module which handles serializing/deserializing RFC-3339 strings using serde and chrono. I have serde's derive feature enabled, and chrono's serde feature enabled.

My code is pretty straightforward:

pub mod rfc3339_millis_utc {
    use chrono::{DateTime, FixedOffset, SecondsFormat, TimeZone, Utc};
    use serde::de::{self, Visitor};
    use serde::{Deserializer, Serializer};
    use std::fmt::{Debug, Display, Formatter};
    use std::str::FromStr;

    /// Serialize a [DateTime] into an RFC-3339 timestamp in UTC with milliseconds and a postfixed
    /// `Z` for the UTC timezone.
    pub fn serialize<S, Z>(value: &DateTime<Z>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
        Z: TimeZone,
    {
        serializer.serialize_str(
            value
                .with_timezone(&Utc)
                .to_rfc3339_opts(SecondsFormat::AutoSi, true)
                .as_str(),
        )
    }

    struct Rfc3339MillisVisitor;

    impl<'de> Visitor<'de> for Rfc3339MillisVisitor {
        type Value = DateTime<Utc>;

        fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
            formatter.write_str("a timestamp in RFC-3339 format, such as 2023-10-01T12:13:14.567Z")
        }

        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            match DateTime::<FixedOffset>::parse_from_rfc3339(v) {
                Ok(d) => Ok(d.with_timezone(&Utc)),
                Err(e) => Err(Error::new(e.to_string())),
            }
        }
    }

    #[derive(Debug)]
    pub struct Error {
        msg: String,
    }

    impl Error {
        pub fn new(msg: String) -> Self {
            Self { msg }
        }
    }

    impl de::Error for Error {
        fn custom<T>(msg: T) -> Self
        where
            T: Display,
        {
            Self {
                msg: msg.to_string(),
            }
        }
    }

    impl std::error::Error for Error {}

    impl Display for Error {
        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
            f.write_str(self.msg.as_ref())
        }
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(Rfc3339MillisVisitor)
    }
}

This doesn't make any sense to me: I've explicitly implemented serde::de::Error for my Error type, I've implemented std::error::Error as well, I've provided a Display and Debug implementation, so I'm not sure why my Error isn't seen as a serde::de::Error implementation, since it is clearly implemented.

What am I doing wrong here?

The caller chooses the type of the generic E, so if you want to return the Err(_) variant, you have to construct some E (via the trait methods) and return that. You're trying to return your own type instead.

One possible fix:

             match DateTime::<FixedOffset>::parse_from_rfc3339(v) {
                 Ok(d) => Ok(d.with_timezone(&Utc)),
-                Err(e) => Err(Error::new(e.to_string())),
+                Err(e) => Err(E::custom(e)),
             }

IOW, you have to consider what would happen if the function were called as visit_str::<NotYourError>()? Clearly returning YourError wouldn't make sense/be possible then.

trait bounds on generic types work differently when you implement a trait, and when you consume the trait.

when you consume the trait, you provide a concrete type that must satisfy the bounds, otherwise it's a compile error.

but when you implement the trait, you must use the same (well, not have to be exactly the same, see below) signature as the trait definition: if the trait is defined with a generic type, your implementation must also be generic.

however, your implementation is allowed to be less restrictive than the trait definition. for example, if the trait definition requires Error + Clone, but your implementation doesn't need the Clone bound, you can leave it out and only specify Error as the required trait bound. but only provide an implementation for a concrete type is not enough, because the actual type argument is choose by the caller, not the implementer.

1 Like

Wow, thank you so much, this is some deep type system stuff that I haven't had to use just yet. I've used GATs to great effect, but I've always been snagged simply trying to de/serialize chrono timestamps. Is there a part of the book or the nomicon that discusses this in more detail? I'm going to have to read your answer a few times, but once again, thanks!

That answer makes it look more mysterious than it really is. Fortunately, there's no "fancy type system stuff" going on. It's elementary logic.

If you say "this function must return a value of type E for all possible choices of E", then the only way to achieve that is to ask E to create an instance of itself. You can't return some concrete type, because E may not be the same as the type you choose to return.

I always like to illustrate this with a parallel from elementary algebra. Let's say you are tasked with writing a function whose contract is that it must add 3 to an integer and return the sum:

fn add_three(x: u64) -> u64 {
    todo!()
}

How would you implement this? Would you do that by simply returning 5? Clearly that doesn't make any sense. But also 5 is clearly 3 more than another number, concretely 2. Why would they whack you in the head for implementing the function by returning 2 + 3? Isn't 2 a perfectly good integer?

Well, your task was to make the function compute x + 3 for all possible x, not just for one specific value of x that you happen to like.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.