shepmaster / snafu

Easily assign underlying errors into domain-specific errors while adding context
https://docs.rs/snafu/
Apache License 2.0
1.4k stars 60 forks source link

Can no longer have a backtrace field when using source(from) where the original type is not an error (breaking change in 0.7.2) #364

Closed jnicholls closed 1 year ago

jnicholls commented 1 year ago

I had an error roughly defined as such:

#[derive(Debug, Snafu)]
enum Error {
    Variant1 {
        #[snafu(source(from(String, Into::into)))]
        source: Box<dyn std::error::Error + Send + Sync>,
    },

    // ...
}

fn return_error() -> Result<(), String> {
    Err("nope!".to_string())
}

return_error().context(Variant1Snafu)?;

In 0.7.1 this was acceptable. In 0.7.2, it is now saying that String does not implement std::error::Error which is a requirement for AsErrorSource. I believe the derive macro is somehow using the from type as the binding for the context, which would be equivalent to trying to have a source: String field directly, which I know has never worked.

shepmaster commented 1 year ago

Repro

main.rs

use snafu::prelude::*;

#[derive(Debug, Snafu)]
enum Error {
    Variant1 {
        #[snafu(source(from(String, Into::into)))]
        source: Box<dyn std::error::Error + Send + Sync>,
    },
}

fn return_error() -> Result<(), String> {
    Err("nope!".to_string())
}

fn demo() -> Result<(), Error> {
    return_error().context(Variant1Snafu)
}

fn main() {}

Cargo.toml

[dependencies]
snafu = "=0.7.1"
snafu-derive = "=0.7.1"

Output

Upgrading snafu-derive to 0.7.2 causes the error

error[E0599]: no function or associated item named `generate_with_source` found for trait object `dyn GenerateImplicitData` in the current scope
    --> /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/snafu-0.7.1/src/lib.rs:1391:17
     |
1391 | #[derive(Debug, Snafu)]
     |                 ^^^^^ function or associated item not found in `dyn GenerateImplicitData`
     |
     = note: this error originates in the derive macro `Snafu` (in Nightly builds, run with -Z macro-backtrace for more info)
shepmaster commented 1 year ago

no function or associated item named generate_with_source found

This occurs if you have snafu = 0.7.1 and snafu-derive = 0.7.2, which is something I intended to work but don't have tests for. That being said...

it is now saying that String does not implement std::error::Error which is a requirement for AsErrorSource

This isn't the error I produced — can you please create a fully contained reproduction?

jnicholls commented 1 year ago

Sorry for not sharing it sooner. This is the message below, built against 0.7.2 of both snafu and snafu-derive.

error[E0599]: the method `as_error_source` exists for struct `std::string::String`, but its trait bounds were not satisfied
  --> src/concatenator.rs:62:17
   |
62 | #[derive(Debug, Snafu)]
   |                 ^^^^^ method cannot be called on `std::string::String` due to unsatisfied trait bounds
   |
   = note: the following trait bounds were not satisfied:
           `std::string::String: StdError`
           which is required by `std::string::String: AsErrorSource`
           `str: Sized`
           which is required by `str: AsErrorSource`
           `str: StdError`
           which is required by `str: AsErrorSource`
   = note: this error originates in the derive macro `Snafu` (in Nightly builds, run with -Z macro-backtrace for more info) 

This is partially my enum:

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("Unable to build an HDD: {source}"))]
    HddBuild {
        backtrace: Option<Backtrace>,
        #[snafu(source(from(String, Into::into)))]
        source: Box<dyn StdError + Send + Sync>,
    },
}
shepmaster commented 1 year ago

Complete example

use snafu::{prelude::*, Backtrace};

#[derive(Debug, Snafu)]
pub struct HddBuildError {
    backtrace: Option<Backtrace>,

    #[snafu(source(from(String, Into::into)))]
    source: Box<dyn std::error::Error + Send + Sync>,
}

fn return_error() -> Result<(), String> {
    Err("nope!".to_string())
}

fn demo() -> Result<(), HddBuildError> {
    return_error().context(HddBuildSnafu)
}

fn main() {}
shepmaster commented 1 year ago

I'll need to dig into the code, but I'm hoping that the fix here is to call the generate_with_source method with the error after the transformation.

shepmaster commented 1 year ago

Yeah, we generate this:

fn into_error(self, error: Self::Source) -> HddBuildError {
    HddBuildError {
        backtrace: {
            use ::snafu::AsErrorSource;
            let error = error.as_error_source();
            ::snafu::GenerateImplicitData::generate_with_source(error)
        },
        source: (Into::into)(error),
    }
}

Should need to move the (Into::into)(error) earlier, save it as error and pass that to generate_with_source

jnicholls commented 1 year ago

Great sleuthing! Thanks for jumping on this one so quickly. 🕵️

shepmaster commented 1 year ago

I want to clean up the commit, but can you try https://github.com/shepmaster/snafu/pull/366 locally and see if that works for your original case?

jnicholls commented 1 year ago

Sorry for the delay. #366 works! Thank you.

shepmaster commented 1 year ago

Released in 0.7.3