More friendly Errors

My project works on a files in a directory. There are many occasions for some I/O error. I use From<> trait to convert io::Error into my more general error type, and I use the “?” operator in the code.

But there is a problem: when I got the i/o error, it is not very informative. It does not say which operation failed.
I am looking for good solution for that. Few options were considered:

1.) Extend MyError type and use .map_err(|e|{…})? to describe every type.
But it makes the code ugly and less readable

2.) Custom variant of old-fashioned try! macro, doing conversion and saving filename+linenumber into the new error. Less elegant than “?” but much better than .map_err everywhere. In most cases filename+linenum is enough for troubleshooting.
I guess I cannot modify behavior of the “?” locally and extend its behaviour.

Other ideas are welcome.

You might like the failure crate for this: https://boats.gitlab.io/failure/

1 Like

The path_abs crate tries to provide better errors that at least have filenames.

Line numbers are tough; there’s no popular API I know of in rust for tracking this information. I think most file-parsing libraries track this on their own.

Thanks, but the Failure crate does not help here.

There are line! and file! macros in std.
I will look deeper at the path_abs crate.

Oh, I might have misinterpreted. I thought you meant the filename and line number of a file your code is reading.

(file!() and line!() give you information about the Rust source file)

Any chance of getting a function! or method! macro to get the name of the function or method the macro was called from? Is there any other way to get that information?

I know you said that the Failure crate does not help, but I wanted to be sure that you saw the Context wrapper:

fn read_from_file() -> Result<String, Error> {

    let mut file = File::open("foo.txt").context("Opening file")?;
    let mut data = String::new();
    file.read_to_string(&mut data).context("Reading data")?;

    Ok(data)
}

When unwrapped or printed, the error will look like this:

Os { code: 2, kind: NotFound, message: "No such file or directory" }

Opening file

The output isn’t very pretty, but this is a way to associate some context with each IO operation with not much extra work.

1 Like

You can also get line info from failure backtraces:
https://docs.rs/failure/0.1.2/failure/#backtraces

You may need to set debuginfo=1 for release builds though.

Shameless plug I made a library (https://crates.io/crates/sourcefile) to concatenate many files into a single block of source, and then resolve offsets in that source to file and line number. Maybe this is helpful for you?

Edit Aah it seems you are working on rust files, yes io::Error doesn’t have much info about the error location, I usually use the following pattern.


extern crate failure;

use failure::ResultExt;

fn a_function() -> Result<_, failure::Error> {
    File::open("/my/path").context("opening /my/path")?;
    // ...
}

The context function is really great, it allows you to add contextual info to the error, kind of like a custom stack trace. If you then print your instance of failure::Error you’ll get all the different bits of contextual information, along with the underlying error.

1 Like

I think the closest existing thing we have is module_path. I mean, if you squint really hard, and give up on trait/struct methods, then you could almost maybe envision a proc_macro attribute like…

#[modulify]
pub fn my_function() {
    println!("{}", module_path!());
}

// ...expands to...

pub use self::my_function::my_function;
mod my_function {
    use super::*;

    pub fn my_function() {
        println!("{}", module_path!());
    }
}

The .context() looks good. I’ll try to use it.

Finally, I’ll use my tiny custom lightweight variant of Failure::context() solution.

                      .-- Result<T, E>
                      v
fs::File::open("a/b/c").tag("lo")?;
                                ^
                                `-- Result<T, ETag<E>>

Code
https://play.rust-lang.org/?gist=ea74f4be547ba9670deacaead6a68bfb&version=stable&mode=debug&edition=2015
Note: when executed outside playground, the result is:

ERROR1: parse error
ERROR2: [rc] IO: Permission denied (os error 13)
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: [rc] IO: Permission denied (os error 13)', libcore/result.rs:945:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.