Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Any easy way to add common implicit data to an error enum? #468

Open
thexiay opened this issue Nov 27, 2024 · 4 comments
Open

Any easy way to add common implicit data to an error enum? #468

thexiay opened this issue Nov 27, 2024 · 4 comments
Labels
support request Help requested on using the project

Comments

@thexiay
Copy link

thexiay commented Nov 27, 2024

currntly, if i want to get a err enum with some same detail info ,i must define them in every enum variants.

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum MyErrorEnum {
    Err1 {
        #[snafu(implicit)]
        backtrace: Backtrace,
        #[snafu(implicit)]
        loc: Location,
        #[snafu(implicit)]
        span: Span,
    },

    Err2 {
        detail: String,
        #[snafu(implicit)]
        backtrace: Backtrace,
        #[snafu(implicit)]
        loc: Location,
        #[snafu(implicit)]
        span: Span,
    },
    
    // ...
}

pub type Result<T> = std::error::Result<T, MyError>;

or like this

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub struct MyError {
    #[snafu(implicit)]
    backtrace: Backtrace,
    #[snafu(implicit)]
    loc: Location,
    #[snafu(implicit)]
    span: Span,
    source: MyErrorEnum,
}

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum MyErrorEnum {
    Err1,
    Err2(String),
    // ...
}

pub type Result<T> = std::error::Result<T, MyError>;

impl<E> From<E> for MetaError
where
    E: Into<MetaErrorEnum>,
{
    #[track_caller]
    fn from(error: E) -> Self {
        Self {
            source: error.into(),
            loc: GenerateImplicitData::generate(),
            span: GenerateImplicitData::generate(),
            backtrace: GenerateImplicitData::generate(),
        }
    }
}

impl MyError {
    pub fn inner(&self) -> &MyErrorEnum {
        // ...
    }
}

Is there an easier way to do this without having to write frustratingly repetitive property values?

@thexiay thexiay changed the title A easy way to acces common data? A easy way to acces common implicit data? Nov 27, 2024
@thexiay thexiay changed the title A easy way to acces common implicit data? Any easy way to acces common implicit data? Nov 27, 2024
@Enet4 Enet4 changed the title Any easy way to acces common implicit data? Any easy way to add common implicit data to an error enum? Nov 27, 2024
@Enet4
Copy link
Collaborator

Enet4 commented Nov 27, 2024

Those would be the two approaches that I know of as well. Perhaps there could be a way to automatically fill in MetaError upon context insertion, but it's worth pointing out that some of the implicit data might only be desirable in leaf error variants.

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum MyErrorEnum {
    Err1 {
        #[snafu(implicit)]
        backtrace: Backtrace,
        #[snafu(implicit)]
        loc: Location,
        #[snafu(implicit)]
        span: Span,
    },
    
    // has a source with the backtrace
    Err2 {
        #[snafu(backtrace)]
        source: BarError,
        #[snafu(implicit)]
        loc: Location,
        #[snafu(implicit)]
        span: Span,
    },
    
    // ...
}

@shepmaster
Copy link
Owner

do this without [writing] repetitive property values?

I appreciate this question because it's in the small set of things I can answer definitely: no. SNAFU is currently implemented as a derive macro and derive macros cannot modify the type they are attached to, they may only output additional code.

It's actually on my long-term roadmap to rewrite to an attribute macro 1 because SNAFU produces additional types (the context selectors) and people generally don't expect a derive macro to do anything beyond implement a trait. Even in that future world, I don't know how I'd feel about SNAFU modifying the fields of the type.

Perhaps some crate out there provides an attribute macro that will automatically add an attribute + field pair to every enum? If so, then you could use it in addition to SNAFU.

In your case, it appears you have multiple implicit fields, so you could create a composite type that implements GenerateImplicitData by delegating off to its children. That way your error definitions would only need one field each, simplifying things a small bit.

Another possibility is to have a wrapping error that contains the implicit data and then always convert the inner error to the wrapping error:

use snafu::prelude::*;

#[derive(Debug, Snafu)]
enum Inner {
    #[snafu(display("Bad thing 1"))]
    Alfa,

    #[snafu(display("Bad thing 2"))]
    Beta,
}

#[derive(Debug, Snafu)]
#[snafu(transparent)]
struct Outer {
    source: Inner,

    #[snafu(implicit)]
    location: snafu::Location,
}

fn inner_main(value: bool) -> Result<(), Outer> {
    if value {
        AlfaSnafu.fail()?;
    } else {
        BetaSnafu.fail()?;
    }

    Ok(())
}

#[snafu::report]
fn main() -> Result<(), Outer> {
    let v = inner_main(std::env::args().count() > 1);
    if let Err(e) = &v {
        eprintln!("It happened at {}", e.location);
    }
    v
}

This will run into some ergonomic problems around type inference sometimes though, so I wouldn't recommend it as a first solution.


#[snafu(implicit)]
backtrace: Backtrace,

Note that you don't need implicit on a backtrace field — it's implicit.


#[snafu(implicit)]
backtrace: Backtrace,
#[snafu(implicit)]
loc: Location,

It seems odd to have both of these as the backtrace should contain the location, no?

Footnotes

  1. This would look like #[snafu] enum Error {} instead of #[derive(Snafu)] enum Error {}.

@shepmaster shepmaster added the support request Help requested on using the project label Nov 27, 2024
@thexiay
Copy link
Author

thexiay commented Nov 28, 2024

do this without [writing] repetitive property values?

I appreciate this question because it's in the small set of things I can answer definitely: no. SNAFU is currently implemented as a derive macro and derive macros cannot modify the type they are attached to, they may only output additional code.

It's actually on my long-term roadmap to rewrite to an attribute macro 1 because SNAFU produces additional types (the context selectors) and people generally don't expect a derive macro to do anything beyond implement a trait. Even in that future world, I don't know how I'd feel about SNAFU modifying the fields of the type.

Perhaps some crate out there provides an attribute macro that will automatically add an attribute + field pair to every enum? If so, then you could use it in addition to SNAFU.

In your case, it appears you have multiple implicit fields, so you could create a composite type that implements GenerateImplicitData by delegating off to its children. That way your error definitions would only need one field each, simplifying things a small bit.

Another possibility is to have a wrapping error that contains the implicit data and then always convert the inner error to the wrapping error:

use snafu::prelude::*;

#[derive(Debug, Snafu)]
enum Inner {
    #[snafu(display("Bad thing 1"))]
    Alfa,

    #[snafu(display("Bad thing 2"))]
    Beta,
}

#[derive(Debug, Snafu)]
#[snafu(transparent)]
struct Outer {
    source: Inner,

    #[snafu(implicit)]
    location: snafu::Location,
}

fn inner_main(value: bool) -> Result<(), Outer> {
    if value {
        AlfaSnafu.fail()?;
    } else {
        BetaSnafu.fail()?;
    }

    Ok(())
}

#[snafu::report]
fn main() -> Result<(), Outer> {
    let v = inner_main(std::env::args().count() > 1);
    if let Err(e) = &v {
        eprintln!("It happened at {}", e.location);
    }
    v
}

This will run into some ergonomic problems around type inference sometimes though, so I wouldn't recommend it as a first solution.

#[snafu(implicit)]
backtrace: Backtrace,

Note that you don't need implicit on a backtrace field — it's implicit.

#[snafu(implicit)]
backtrace: Backtrace,
#[snafu(implicit)]
loc: Location,

It seems odd to have both of these as the backtrace should contain the location, no?

Footnotes

  1. This would look like #[snafu] enum Error {} instead of #[derive(Snafu)] enum Error {}.

yep, maybe attribute macro is more flexible. I use the second way to wrap the common implict data, but I write a lot of other convert function manaually, it's a littele bit weird, how I wish they were automatically generated😁

@yuanyan3060
Copy link

currntly, if i want to get a err enum with some same detail info ,i must define them in every enum variants.

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum MyErrorEnum {
    Err1 {
        #[snafu(implicit)]
        backtrace: Backtrace,
        #[snafu(implicit)]
        loc: Location,
        #[snafu(implicit)]
        span: Span,
    },

    Err2 {
        detail: String,
        #[snafu(implicit)]
        backtrace: Backtrace,
        #[snafu(implicit)]
        loc: Location,
        #[snafu(implicit)]
        span: Span,
    },
    
    // ...
}

pub type Result<T> = std::error::Result<T, MyError>;

or like this

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub struct MyError {
    #[snafu(implicit)]
    backtrace: Backtrace,
    #[snafu(implicit)]
    loc: Location,
    #[snafu(implicit)]
    span: Span,
    source: MyErrorEnum,
}

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum MyErrorEnum {
    Err1,
    Err2(String),
    // ...
}

pub type Result<T> = std::error::Result<T, MyError>;

impl<E> From<E> for MetaError
where
    E: Into<MetaErrorEnum>,
{
    #[track_caller]
    fn from(error: E) -> Self {
        Self {
            source: error.into(),
            loc: GenerateImplicitData::generate(),
            span: GenerateImplicitData::generate(),
            backtrace: GenerateImplicitData::generate(),
        }
    }
}

impl MyError {
    pub fn inner(&self) -> &MyErrorEnum {
        // ...
    }
}

Is there an easier way to do this without having to write frustratingly repetitive property values?

https://github.com/yuanyan3060/enum_expand

Maybe you can try this crate, and if it works, I'll upload it to crate.io

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
support request Help requested on using the project
Projects
None yet
Development

No branches or pull requests

4 participants