I am writing a custom Serializer for serde.
For it, I also need a custom error type.
Serde requires me to implement serde::ser::Error
for this.
serde::ser::Error
in turn requires me to implement a custom()
method where the parameter is only limited by the std::fmt::Display
trait bound.
So I came up with this:
use std::fmt::{Debug, Display, Formatter};
#[derive(Debug)]
pub enum Error {
Custom(Box<dyn Display>),
CannotSerializeMap,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Custom(error) => Display::fmt(error, f),
Self::CannotSerializeMap => write!(f, "cannot serialize map"),
}
}
}
impl std::error::Error for Error {}
impl serde::ser::Error for Error {
fn custom<T>(msg: T) -> Self
where
T: Display,
{
Self::Custom(Box::new(msg))
}
}
However, I cannot derive Debug
, which is necessary for std::error::Error
which in turn is necessary for serde::ser::Error
, since Box<dyn Display>
does not implement Debug
.
I cannot implement Debug
for it, since neither Box
nor the trait Debug
itself are in my crate.
I also cannot add an appropriate constraint for T
in custom()
since it would break the trait's contract.
How can I solve this?
You can use a little trick, based on two facts:
Display
also implements ToString
(by formatting the value with Display
into the empty string).Box<dyn Error>
from String
with a From
implementation.Therefore, whenever you have some T: Display
, you can instead of Box<dyn Display>
get a Box<dyn Error>
, like this (playground):
use std::fmt::{Debug, Display, Formatter};
#[derive(Debug)]
pub enum Error {
Custom(Box<dyn std::error::Error>),
CannotSerializeMap,
}
impl serde::ser::Error for Error {
fn custom<T>(msg: T) -> Self
where
T: Display,
{
Self::Custom(msg.to_string().into())
}
}
And then #[derive(Debug)]
works out of the box (pun unintended), since dyn Error
is Debug
.