from Hacker News

Enhance Rust errors with file and line details

by JoelJacobson on 8/23/23, 5:37 AM with 91 comments

  • by afdbcreid on 8/23/23, 12:59 PM

    I don't like having to wrap my function with a macro, with all disadvantages (e.g. worse IDE support), just to get error location. Especially given we can do this without macros at all:

        use std::error::Error as StdError;
        use std::panic::Location;
        use std::fmt;
    
        #[derive(Debug)]
        pub struct MyError {
            pub error: Box<dyn StdError + Send + Sync>,
            pub location: &'static Location<'static>,
        }
        
        impl<E: StdError + Send + Sync + 'static> From<E> for MyError {
            #[track_caller]
            #[inline]
            fn from(error: E) -> Self {
                Self {
                    error: Box::new(error),
                    location: Location::caller(),
                }
            }
        }
        
        impl fmt::Display for MyError {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "{}, {}", self.error, self.location)
            }
        }
    
    We can also optimize this more (e.g. a simple optimization to be two words instead of three: https://play.rust-lang.org/?version=stable&mode=debug&editio.... Also allows opting `Send` and `Sync` out or using non-`'static` lifetimes), but this works as a baseline. The only disadvantage is that if called in a function with `#[track_caller]` this will report the location of the caller... But this is something I can live with.
  • by diarrhea on 8/23/23, 7:24 AM

    Reminds me of `diags` from [0].

    Side note: can we make 'file not found' errors that fail to mention what wasn't found illegal? What's the point of 'file not found', then keeping silent about the exact file? Seen it in various ecosystems now.

    [0]: https://www.lurklurk.org/effective-rust/macros.html#when-to-...

  • by dathinab on 8/23/23, 7:31 AM

    Note that some things, like anyhow do create backtraces (if the feature is enabled) when they are created, including by `?` auto-converting into them and including chaining the backtrace from the error they where created from.

    Through also note that async code has ... not so nice backtraces, for now, even if you only care about the calling location.

  • by tasn on 8/23/23, 10:27 AM

    We opted for a more manual approach, we have a ctx!() macro[1] we use for wrapping errors we want to enrich that we then use like this[2]: ctx!(some_fallible_fund(foo))?

    I wodner if anyone is doing anything better? The nice thing about our approach is that we store the data on each error, so we get a full backtrace, even when using async.

    1: https://github.com/svix/svix-webhooks/blob/main/server/svix-...

    2: https://github.com/svix/svix-webhooks/blob/main/server/svix-...

  • by JoelJacobson on 9/2/23, 9:13 AM

    Just released `wherr` v0.1.7! Now with optional `anyhow` support via feature flag. This lets you combine `anyhow`'s error handling with `wherr`'s file and line details seamlessly. Check out the example in `wherr/examples/anyhow.rs`.
  • by imetatroll on 8/23/23, 7:19 AM

    There must be some downside to doing this? That is, why wouldn't the '?' operator already give us this kind of important detail?
  • by dannymi on 8/23/23, 7:26 AM

    Errors are for the regular case when the program detected that something wasn't allowed or set up right and the program did everything right (by returning an error). Why then do you need the rs source code file&line of the place where everything went right? What about all the other places where everything went right? Wanna log those, too? :)

    What will the end user do with rs source code file&line?

    I read the example on the page. What they want to do is return which argument of `add` the error was with. Well, then introduce an `AddError` (or I guess it could be a more generic `BinaryOperationError`) like this: `struct AddError { argument_1: Option<...>, argument_2: Option<...> }` and return that error from `add`. Then you can even have it return errors due to both arguments at the same time.

    One use case of the crate would be if you made a mistake and something actually shouldn't be an error but should be a panic, and you wanna find where it is in order to change it to a panic. So, debugging.

  • by lewantmontreal on 8/23/23, 8:53 AM

    I’ve always wondered why the backtrace just kind of stops midway to when you finally handle an error, instead of showing up to the `?` where the error happened. I’ve been using anyhow’s `with_context` to add helpful notes to each usage of `?` to get around it.

    I’ll have to check if this macro is an easier solution!

  • by olih_ on 8/23/23, 9:09 AM

    This looks great! Is there a way to enable this automatically for a whole crate, without needing to set it per function?
  • by Animats on 8/23/23, 7:08 AM

    Ah, like automatically adding .context("msg") to Return values.
  • by sidit77 on 8/23/23, 10:28 AM

    Any reason why you implemented your own proc macro instead of just using std::panic::Location::caller()? Right now I don't see the advantage of this crate.