thiserror icon indicating copy to clipboard operation
thiserror copied to clipboard

Consider allowing extra fields in variants with #[from] attribute by specifying default values

Open brunoczim opened this issue 1 year ago • 0 comments

I am facing this situation:

#[derive(Debug, Error)]
pub enum Error {
    #[error(
        "{}{}{}",
        match .path { Some(ref path) => &path[..], None => "" },
        match .path { Some(_) => ": ", None => "" },
        .source,
    )]
    Tera {
        path: Option<String>,
        #[from]
        #[source]
        source: tera::Error,
    },
   // other variants omitted
}

Which yields this error:

error: deriving From requires no fields other than source and backtrace
  --> src/lib.rs:26:9
   |
26 |         #[from]
   |         ^^^^^^^

However, there is a pretty reasonable implementation of From<tera::Error> for Error: make this extra field path be constructed with None. Something like:

impl From<tera::Error> for Error {
    fn from(source: tera::Error) -> Self {
        Self::Tera { path: None, source }
    }
}

I could implement it manually, but it feels like it's a work that the procedural macro could be doing. Not only in my situation, but also in many similar situations, From could be automatically implemented constructing extra fields with some given default values. I'd propose this API:

#[derive(Debug, Error)]
pub enum Error {
    #[error(
        "{}{}{}",
        match .path { Some(ref path) => &path[..], None => "" },
        match .path { Some(_) => ": ", None => "" },
        .source,
    )]
    Tera {
        #[default(None)]
        path: Option<String>,
        #[from]
        #[source]
        source: tera::Error,
    },
   // other variants omitted
}

And also, to improve ergonomics, if the default value is omitted from the attribute, the proc-macro could generate Default::default() as the constructed value. For instance, since None == Default::default(), the code above would be equivalent to the code below:

#[derive(Debug, Error)]
pub enum Error {
    #[error(
        "{}{}{}",
        match .path { Some(ref path) => &path[..], None => "" },
        match .path { Some(_) => ": ", None => "" },
        .source,
    )]
    Tera {
        #[default]
        path: Option<String>,
        #[from]
        #[source]
        source: tera::Error,
    },
   // other variants omitted
}

I intend to implement this myself if I find some time to do it, but I need feedback first. Is this feature welcome? If I open a PR with this, would it be welcome?

brunoczim avatar Feb 25 '24 21:02 brunoczim