Is there a way to get SNAFU's `.backtrace()` on arbitrary `&dyn std::error::Error` trait objects? - error-handling

RFC 2504 will add a required fn backtrace(&self) -> Option<&Backtrace> to all std::error::Error. This is not ready yet, so for now, SNAFU, an error helper macro, polyfills this by tying an ErrorCompat trait to all types generated by the macro. This allows for backtrace support before it lands in Rust nightly.
However, this ErrorCompat trait is not implemented for all implementors of std::error::Error. I want to — in some generic error printing code — be able to display the chain of causes along with the stacktrace associated with where the SNAFU error was instantiated. Unfortunately, the source() function returns &(dyn Error + 'static).
use std::error::Error as StdError;
use snafu::{ResultExt, ErrorCompat};
fn main() {
let err: Result<(), _> = Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"));
let err = err.with_context(|| parse_error::ReadInput {
filename: "hello"
});
let err = err.with_context(|| compile_error::ParseStage);
// some generic error handling code
if let Err(err) = err {
// `cause` is type &(dyn std::error::Error + 'static)
let cause = err.source().unwrap();
if let Some(err) = /* attempt to downcast cause into &dyn snafu::ErrorCompat trait object */ {
println!("{}", err.backtrace().unwrap());
}
}
}
pub mod compile_error {
use snafu::{Snafu, Backtrace};
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(super)))]
pub enum Error {
#[snafu(display("Error parsing code: {}", source))]
ParseStage {
source: crate::parse_error::Error,
backtrace: Backtrace
},
}
}
pub mod parse_error {
use snafu::{Snafu, Backtrace};
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(super)))]
pub enum Error {
#[snafu(display("Could not read input {:?}: {}", filename, source))]
ReadInput {
filename: std::path::PathBuf,
source: std::io::Error,
backtrace: Backtrace
},
}
}
I've looked at std::any::Any::downcast_ref but this is for downcasting to a struct, not downcasting a trait object to another trait object. I'd like to avoid having to list out all possible concrete-typed SNAFU errors in my error-handling code.
I could cryo-freeze myself until RFC 2504 is (fully) implemented but surely there's some way to do this.

A dyn Error has the methods of Error and nothing else. If the backtrace cannot be deduced from those methods then where else could that information come from?
Unfortunately RFC 2504 is not yet stabilised, so you will need to be cryogenically frozen until at least Rust 1.39 if you want to wait for it.

It seems I missed this because nightly std docs weren't recompiled, but #![feature(backtrace)] is in nightly right now. SNAFU still needs to add support for it, so I'm still stuck on getting this all working.

Related

What is the correct error part of the Result type of a function propagating different error types?

I tried to write a function, that shall propagate different error types. Only for example see the following code:
use std::fs::File;
use std::io;
use std::io::Read;
fn main() {
let number = read_number_from_file().unwrap();
println!("Read number {}!", number);
}
// So what is the correct error part of the Result<i32, ...>?
fn read_number_from_file() -> Result<i32, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
let number = s.parse()?;
Ok(number)
}
Compiling it results in the following error unsurprisingly:
error[E0277]: `?` couldn't convert the error to `std::io::Error`
--> src\main.rs:16:27
|
16 | let number = s.parse()?;
| ^ the trait `std::convert::From<std::num::ParseIntError>` is not implemented for `std::io::Error`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= help: the following implementations were found:
<std::io::Error as std::convert::From<std::ffi::NulError>>
<std::io::Error as std::convert::From<std::io::ErrorKind>>
<std::io::Error as std::convert::From<std::io::IntoInnerError<W>>>
= note: required by `std::convert::From::from`
So what is the correct Error type of the Result type? Is there an answer not only for this specific case: I have a function with several calls to other functions returning Result<T, E> which in the error case shall be propagated out of the function to the caller but have different Types E?
You should define your own error type, which includes all possible errors that could happen:
#[derive(Debug)]
enum Error {
Io(io::Error),
ParseInt(num::ParseIntError)
}
impl From<io::Error> for Error {
fn from(other: io::Error) -> Error {
Error::Io(other)
}
}
impl From<num::ParseIntError> for Error {
fn from(other: num::ParseIntError) -> Error {
Error::ParseInt(other)
}
}
The From conversions allow the ? operator to work as expected.
You can save a lot of typing by using one of the several crates that will generate the boilerplate for you. Some popular ones are thiserror and snafu.
I prefer thiserror because it only adds the implementations that you would otherwise write yourself and, if you are writing a library, it is transparent to your library's users. snafu is arguably more powerful - it generates a lot more code, but is opinionated in what it generates, so you will need to get used to its paradigm in order to take full advantage of it, and the snafu concepts will become part of your library's API.
Using thiserror, the code above is reduced to:
use thiserror::Error;
#[derive(Debug, Error)]
enum Error {
#[error("IO Error: {0})]
Io(#[from] io::Error),
#[error("ParseInt Error: {0})]
ParseInt(#[from] num::ParseIntError)
}
Note that this is also generating Display implementations, which I didn't include above.

How to match custom Fails with the failure crate

I'm trying to understand how to use the failure crate. It works splendidly as a unification of different types of standard errors, but when creating custom errors (Fails), I do not understand how to match for custom errors. For example:
use failure::{Fail, Error};
#[derive(Debug, Fail)]
pub enum Badness {
#[fail(display = "Ze badness")]
Level(String)
}
pub fn do_badly() -> Result<(), Error> {
Err(Badness::Level("much".to_owned()).into())
}
#[test]
pub fn get_badness() {
match do_badly() {
Err(Badness::Level(level)) => panic!("{:?} badness!", level),
_ => (),
};
}
fails with
error[E0308]: mismatched types
--> barsa-nagios-forwarder/src/main.rs:74:9
|
73 | match do_badly() {
| ---------- this match expression has type `failure::Error`
74 | Err(Badness::Level(level)) => panic!("{:?} badness!", level),
| ^^^^^^^^^^^^^^^^^^^^^ expected struct `failure::Error`, found enum `Badness`
|
= note: expected type `failure::Error`
found type `Badness`
How can I formulate a pattern which matches a specific custom error?
You need to downcast the Error
When you create a failure::Error from some type that implements the Fail trait (via from or into, as you do), you temporarily hide the information about the type you're wrapping from the compiler. It doesn't know that Error is a Badness - because it can also be any other Fail type, that's the point. You need to remind the compiler of this, the action is called downcasting. The failure::Error has three methods for this: downcast, downcast_ref and downcast_mut. After you've downcast it, you can pattern match on the result as normal - but you need to take into account the possibility that downcasting itself may fail (if you try to downcast to a wrong type).
Here's how it'd look with downcast:
pub fn get_badness() {
if let Err(wrapped_error) = do_badly() {
if let Ok(bad) = wrapped_error.downcast::<Badness>() {
panic!("{:?} badness!", bad);
}
}
}
(two if lets can be combined in this case).
This quickly gets very unpleasant if more than one error type needs to be tested, since downcast consumes the failure::Error it was called on (so you can't try another downcast on the same variable if the first one fails). I sadly couldn't figure out an elegant way to do this. Here's a variant one shouldn't really use (panic! in map is questionable, and doing anything else there would be plenty awkward, and I don't even want to think about more cases than two):
#[derive(Debug, Fail)]
pub enum JustSoSo {
#[fail(display = "meh")]
Average,
}
pub fn get_badness() {
if let Err(wrapped_error) = do_badly() {
let e = wrapped_error.downcast::<Badness>()
.map(|bad| panic!("{:?} badness!", bad))
.or_else(|original| original.downcast::<JustSoSo>());
if let Ok(so) = e {
println!("{}", so);
}
}
}
or_else chain should work OK if you actually want to produce some value of the same type from all of the possible\relevant errors. Consider also using non-consuming methods if a reference to the original error is fine for you, as this would allow you to just make a series of if let blocks , one for each downcast attempt.
An alternative
Don't put your errors into failure::Error, put them in a custom enum as variants. It's more boilerplate, but you get painless pattern matching, which the compiler also will be able to check for sanity. If you choose to do this, I'd recommend derive_more crate which is capable of deriving From for such enums; snafu looks very interesting as well, but I have yet to try it. In its most basic form this approach looks like this:
pub enum SomeError {
Bad(Badness),
NotTooBad(JustSoSo),
}
pub fn do_badly_alt() -> Result<(), SomeError> {
Err(SomeError::Bad(Badness::Level("much".to_owned())))
}
pub fn get_badness_alt() {
if let Err(wrapper) = do_badly_alt() {
match wrapper {
SomeError::Bad(bad) => panic!("{:?} badness!", bad),
SomeError::NotTooBad(so) => println!("{}", so),
}
}
}

Why can't the Option.expect() message be downcast as a &'static str when a panic is handled with catch_unwind?

I have the following code:
use std::thread;
use std::panic;
pub fn main(){
thread::spawn(move || {
panic::catch_unwind(|| {
// panic!("Oh no! A horrible error.");
let s: Option<u32> = None;
s.expect("Nothing was there!");
})
})
.join()
.and_then(|result| {
match result {
Ok(ref val) => {
println!("No problems. Result was: {:?}", val);
}
Err(ref err) => {
if let Some(err) = err.downcast_ref::<&'static str>() {
println!("Error: {}", err);
} else {
println!("Unknown error type: {:?}", err);
}
}
}
result
});
}
When I trigger a panic! directly (by uncommenting the line in the code above), then I get an output which includes my error message:
Error: Oh no! A horrible error.
But, if I use Option::expect(&str), as above, then the message cannot be downcast to &'static str, so I can't get the error message out:
Unknown error type: Any
How can I get the error message, and how would I find the correct type to downcast to in the general case?
Option::expect expects a message as a &str, i.e. a string slice with any lifetime. You can't coerce a &str to a &'static str, as the string slice may refer to the interior of a String or Box<str> that could be freed at any time. If you were to keep a copy of the &'static str around, you would be able to use it after the String or Box<str> has been dropped, and that would be undefined behavior.
An importail detail is that the Any trait cannot hold any lifetime information (hence the 'static bound), as lifetimes in Rust are erased at compile time. Lifetimes are used by the compiler to validate your program, but a program cannot distinguish a &'a str from a &'b str from a &'static str at runtime.
[...] how would I find the correct type to downcast to in the general case?
Unfortunately, it's not easy. Any has a method (unstable as of Rust 1.15.1) named get_type_id that lets you obtain the TypeId of the concrete object referred to by the Any. That still doesn't tell you explicitly what type that is, as you still have to figure out which type this TypeId belongs to. You would have to get the TypeId of many types (using TypeId::of) and see if it matches the one you got from the Any, but you could do the same with downcast_ref.
In this instance, it turns out that the Any is a String. Perhaps Option::expect could eventually be specialized such that it panics with the string slice if its lifetime is 'static and only allocates a String if it's not 'static.
Like Francis said, you can't in general discover and cast to the type of a panic. However, that being said, panics have the following rules:
If you panic! with a single argument, the panic will have that type. Typically this is &'static str.
If you panic! with more than one argument, the arguments will be treated as format! parameters and used to create a String argument.
These rules are documented in the panic documentation: https://doc.rust-lang.org/std/panic/fn.catch_unwind.html.
With these rules in mind, we can write a function to extract the message from a panic in any case where there is a message available to be extracted, which in practice works most of the time, because most of the time the message is either &'static str or String:
pub fn get_panic_message(panic: &Box<dyn Any + Send>) -> Option<&str> {
panic
// Try to convert it to a String, then turn that into a str
.downcast_ref::<String>()
.map(String::as_str)
// If that fails, try to turn it into a &'static str
.or_else(|| panic.downcast_ref::<&'static str>().map(Deref::deref))
}
I use this exact function in an assertions library I wrote a while ago; you can see some examples of its use in the relevant test suite.

Are there any conventions for aggregating multiple errors as the causes of another error?

I'm writing a function that iterates over a vector of Result and returns success if they all were successful, or an error if any failed. Limitations in error::Error are frustrating me and I'm not sure how to work around them. Currently I have something like:
let mut errors = Vec::new();
for result in results {
match result {
Err(err) => errors.push(err),
Ok(success) => { ... }
}
}
if errors.is_empty() {
return Ok(())
else {
return Err(MyErrorType(errors))
}
The problem with my current approach is that I can only set one error to be the cause of MyErrorType, and my error's description needs to be a static String so I can't include the descriptions of each of the triggering failures. All of the failures are potentially relevant to the caller.
There is no convention that I know of, and indeed I have never had the issue of attempting to report multiple errors at once...
... that being said, there are two points that may help you:
There is no limitation that the description be a 'static String, you are likely confusing &'static str and &str. In fn description(&self) -> &str, the lifetime of str is linked to the lifetime of self (lifetime elision) and therefore an embedded String satisfies the constraints
Error is an interface to deal with errors uniformly. In this case, indeed, only a single cause was foreseen, however it does not preclude a more specific type to aggregate multiple causes and since Error allows downcasting (Error::is, Error::downcast, ...) the more specific type can be retrieved by the handler and queried in full
As such, I would suggest that you create a new concrete type solely dedicated to holding multiple errors (in a Vec<Box<Error>>), and implementing the Error interface. It's up to you to decide on the description and cause it will expose.
A single type will let your clients test more easily for downcasting than having an unknown (and potentially growing as time goes) number of potential downcast targets.
expanding a bit on point 1 of Matthieu's good answer.
The point where you're likely running into trouble (I know I did when I tried to implement Error) is that you want to have a dynamic description().
// my own error type
#[derive(Debug)] struct MyError { value: u8 }
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "bummer! Got a {}", self.value)
}
}
// I am now tempted to add the problematic value dynamically
// into the description, but I run into trouble with lifetimes
// this DOES NOT COMPILE because the String I'm building
// goes out of scope and I can't return a reference to it
impl error::Error for MyError {
fn description(&self) -> &str {
&format!("can't put a {} here!", self.value)
}
}
solution 1
Don't dynamically build description(). Just use a static str. This is what most implementations of Error on github seem to do.
If you need to retrieve and display (or log) the value you can always access it from your MyError type. Plus Display (that you must implement for all Error impls) does allow you to create dynamic strings.
I created a contrived example on the playground that shows how to track multiple errors.
solution 2
(what Matthieu is suggesting) you can store the error message in the error itself.
#[derive(Debug)] struct MyError { value: u8, msg: String }
impl MyError {
fn new(value: u8) -> MyError {
MyError { value: value, msg: format!("I don't like value {}", value) }
}
}
// now this works because the returned &str has the same lifetime
// as self
impl error::Error for MyError {
fn description(&self) -> &str {
&self.msg
}
}

std::error::FromError idiomatic usage

I'm trying to involve std::error::FromError trait as widely as possible in my projects to take advantage of try! macro. However, I'm a little lost with these errors conversions between different mods.
For example, I have mod (or crate) a, which has some error handling using it's own Error type, and implements errors conversion for io::Error:
mod a {
use std::io;
use std::io::Write;
use std::error::FromError;
#[derive(Debug)]
pub struct Error(pub String);
impl FromError<io::Error> for Error {
fn from_error(err: io::Error) -> Error {
Error(format!("{}", err))
}
}
pub fn func() -> Result<(), Error> {
try!(writeln!(&mut io::stdout(), "Hello, world!"));
Ok(())
}
}
I also have mod b in the same situation, but it implements error conversion for num::ParseIntError:
mod b {
use std::str::FromStr;
use std::error::FromError;
use std::num::ParseIntError;
#[derive(Debug)]
pub struct Error(pub String);
impl FromError<ParseIntError> for Error {
fn from_error(err: ParseIntError) -> Error {
Error(format!("{}", err))
}
}
pub fn func() -> Result<usize, Error> {
Ok(try!(FromStr::from_str("14")))
}
}
Now I'm in my current mod super, which has it's own Error type, and my goal is to write a procedure like this:
#[derive(Debug)]
struct Error(String);
fn func() -> Result<(), Error> {
println!("a::func() -> {:?}", try!(a::func()));
println!("b::func() -> {:?}", try!(b::func()));
Ok(())
}
So I definitely need to implement conversions from a::Error and b::Error for my Error type:
impl FromError<a::Error> for Error {
fn from_error(a::Error(contents): a::Error) -> Error {
Error(contents)
}
}
impl FromError<b::Error> for Error {
fn from_error(b::Error(contents): b::Error) -> Error {
Error(contents)
}
}
Ok, it works up until that time. And now I need to write something like this:
fn another_func() -> Result<(), Error> {
let _ = try!(<usize as std::str::FromStr>::from_str("14"));
Ok(())
}
And here a problem raises, because there is no conversion from num::ParseIntError to Error. So it seems that I have to implement it again. But why should I? There is a conversion implemented already from num::ParseIntError to b::Error, and there is also a conversion from b::Error to Error. So definitely there is a clean way for rust to convert one type to another without my explicit help.
So, I removed my impl FromError<b::Error> block and tried this blanket impl instead:
impl<E> FromError<E> for Error where b::Error: FromError<E> {
fn from_error(err: E) -> Error {
let b::Error(contents) = <b::Error as FromError<E>>::from_error(err);
Error(contents)
}
}
And it's even worked! However, I didn't succeed to repeat this trick with a::Error, because rustc started to complain about conflicting implementations:
experiment.rs:57:1: 62:2 error: conflicting implementations for trait `core::error::FromError` [E0119]
experiment.rs:57 impl<E> FromError<E> for Error where a::Error: FromError<E> {
experiment.rs:58 fn from_error(err: E) -> Error {
experiment.rs:59 let a::Error(contents) = <a::Error as FromError<E>>::from_error(err);
experiment.rs:60 Error(contents)
experiment.rs:61 }
experiment.rs:62 }
experiment.rs:64:1: 69:2 note: note conflicting implementation here
experiment.rs:64 impl<E> FromError<E> for Error where b::Error: FromError<E> {
experiment.rs:65 fn from_error(err: E) -> Error {
experiment.rs:66 let b::Error(contents) = <b::Error as FromError<E>>::from_error(err);
experiment.rs:67 Error(contents)
experiment.rs:68 }
experiment.rs:69 }
I can even understand the origin of problem (one type FromError<E> can be implemented both for a::Error and b::Error), but I can't get how to fix it.
Theoretically, maybe this is a wrong way and there is another solution for my problem? Or I still have to repeat manually all errors conversions in every new module?
there is no conversion from num::ParseIntError to Error
It does seem like you doing the wrong thing, conceptually. When a library generates an io::Error, like your first example, then it should be up to that library to decide how to handle that error. However, from your question, it sounds like you are generating io::Errors somewhere else and then wanting to treat them as the first library would.
This seems very strange. I wouldn't expect to hand an error generated by library B to library A and say "wrap this error as if you made it". Maybe the thing you are doing should be a part of the appropriate library? Then it can handle the errors as it normally would. Perhaps you could just accept a closure and call the error-conversion as appropriate.
So definitely there is a clean way for Rust to convert one type to another without my explicit help.
(Emphasis mine). That seems really scary to me. How many steps should be allowed in an implicit conversion? What if there are multiple paths, or even if there are cycles? Having those as explicit steps seems reasonable to me.
I can even understand the origin of problem [...], but I can't get how to fix it.
I don't think it is possible to fix this. If you could implement a trait for the same type in multiple different ways, there's simply no way to pick between them, and so the code is ambiguous and rejected by the compiler.