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

How to make Error cloneable and support context chaining #240

Closed
FredrikNoren opened this issue May 27, 2022 · 3 comments
Closed

How to make Error cloneable and support context chaining #240

FredrikNoren opened this issue May 27, 2022 · 3 comments

Comments

@FredrikNoren
Copy link

FredrikNoren commented May 27, 2022

Hi,

I have a situation where I need an Error to be Cloneable. I saw #7 and tried implementing something like this:

#[derive(Clone, Error)]
pub struct AssetError(Arc<anyhow::Error>);
impl From<anyhow::Error> for AssetError {
    fn from(err: anyhow::Error) -> Self {
        Self(Arc::new(err))
    }
}
impl std::fmt::Display for AssetError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}
impl std::fmt::Debug for AssetError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}


fn a() -> Result<(), AssetError> {
    Err(anyhow!("a").into())
}

fn b() -> Result<(), AssetError> {
    Ok(a().context("b")?)
}

fn c() -> Result<(), AssetError> {
    Ok(b().context("c")?)
}

fn main() {
   if let Err(e) = c() {
      eprintln!("{:#}", e); // Prints: "c: b", i.e. a is left out
   }
}

The one problem though is that it will only store the "latest" two contexts. Any idea how I could change that implementation to store the full context? Here's a playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2172afa004deb537f4c27d5e7bf54bc3

@FredrikNoren FredrikNoren changed the title How to make Error cloneable and support context How to make Error cloneable and support context chaining May 27, 2022
@dtolnay
Copy link
Owner

dtolnay commented May 27, 2022

You haven't implemented a source method in AsserError's Error impl, so it's not possible for errors of type AssetError to be chained / be context for another error.

For your use case it sounds like you want #[error(transparent)] on AssetError (and remove the Display impl), since the source would need to be the same as the Arc<anyhow::Error>'s source.

In general you would need a #[source] attribute somewhere in the error in order to create chains of errors/contexts.

@FredrikNoren
Copy link
Author

@dtolnay Awesome, that works. Thank you!

For anyone else seeing this, here's the final implementation I use:

#[derive(Clone, Error)]
#[error(transparent)]
pub struct AssetError(Arc<anyhow::Error>);
impl From<anyhow::Error> for AssetError {
    fn from(err: anyhow::Error) -> Self {
        Self(Arc::new(err))
    }
}
impl std::fmt::Debug for AssetError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

@phiresky
Copy link

phiresky commented Jul 1, 2023

Just wanted to mention this since I had the same issue:

If you're not using thiserror as well as anyhow, instead of using error(transparent) you can implement StdError manually like this:

impl std::error::Error for AssetError {
  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
    self.inner.source()
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants