'Rust: how to override the default way of reporting error?

When error occurs in Rust, most Rust standard and 3rd party libraries (e.g. anyhow) would simply print the error message to stdout/stderr. What I need is to override it with my own function. For example, call Windows API MessageBox with the location information (which file and line does it panic).

So far, I tried several methods and each lacks something.

  1. Write my custom error, and overrides its Debug trait. The problem is that this error type needs to be able to wrap any underlying error (e.g. dyn std::error::Error), so that I could still write
fn test() -> std::result::Result<(), CustomError> {
  ...
  if (has_windows_error) {
    return Err(WindowsError(123))?;
  }
  if (has_io_error) {
    return Err(IoError(123))?;
  }

  Ok(())
}

However, this means CustomError needs to implement both std::error::Error and From<std::error::Error>, which conflicts against each other.

  1. Write my custom result type, and implement Try. This approach requires touching the experimental #![feature(try_trait_v2)].

  2. Use std::panic::set_panic_hook(), then never use ? in the code. Instead, only immediately unwrap() all Results. This is needed because if I allow the error to bubble up, I will lose the location information in PanicInfo.

I'm relatively new to Rust, so I could have been missing many things. Option 3 is what I'm currently using, though I don't like how it forces me to abandon idiomatic coding style.

What is the cleanest, less intrusive way to implement custom error outputting currently?



Solution 1:[1]

Option 3 is most certainly not the way to go here. In Rust panics mean unrecoverable errors, and from my experience with the community over the last 2 years this guideline is taken pretty seriously. Moreover options 1 and 2 add pretty substantial side-effects to operations which were never meant to hide stuff like that.

Also I'm not sure what you mean by "3rd party libraries (e.g. anyhow) would simply print the error message to stdout/stderr." If your function returns an anyhow::Result<T> and an error occurs, then all that happens is that error is converted into anyhow::Error, it's not printed unless you print it.

The bottom line is that there's no "error handling framework" in Rust. Results are not special at all, in fact I encourage you to read the source code. If you want to handle a result in a certain way, then match on it and write the code to handle it. Moreover the panic handling machinery is not meant to elevate them to something like C++ exceptions. You can read more about the motivations for it here.

It sounds like what would work best for you is using anyhow or something like it, and at some point in your program you can match on a result, convert the anyhow::Error (or std::error::Error implementer of your choice) to a string and open a message box. If you want to include file and line information about where the error occurred, then that could be added to the error's message/data via the file! and line! macros provided through the standard library. This could look something like the following:

pub fn main() {
    if let Err(error) = try_main() {
        // show_message_box implementation not shown
        show_message_box(error.to_string());
    }
}

pub fn try_main() -> anyhow::Result<()> {
    // code which makes extensive use of `?`
}

Note that Rust's approach to error handling is different from many other widely used languages, so it's going to require a shift in coding style. Errors are deliberately meant to be intrusive in your code so you actually handle them properly, which is something I came to greatly appreciate with time. In fact, Rust's idiosyncrasies make it a very opinionated language, so when you reach pain points I would advice against trying to solve them by using language features beyond their intended use, as that's more likely to cause additional pain points than to solve your problem.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ian S.