Detailed IO errors

I have functions like this:

fn run() -> Result<(), Box<Error>> {
    let file = File::create("post.txt")?;

    ...

    Ok(())
}

But the error messages from Display-ing the file open / write / etc. errors are terribly vague. How would I make the errors more detailed without complicating the code with error handling statements everywhere?

Right now the errors resemble Permission denied (os error 13), but I want them to look like Could not create file 'post.txt' (permission denied). or something at least as informative and readable.

The easiest is probably to add context using map_err:

fn run() -> Result<(), Box<Error>> {
    let filename = "post.txt";
    let file = File::create(filename).map_err(|e| {
        format!("Could not create file '{}': {}", filename, e)
    })?;
    Ok(())
}

For something a bit more flexible and polished, look at the error_chain crate: error_chain - Rust

Yeah that's what I meant by adding extra error handling statements everywhere. There probably isn't a way better than that without wrapping the entire fs crate module though.

Edit: Nevermind I take it all back error-chain is amazing. :smiley:

1 Like

How did error-chain help you? I'm familiar with foreign-links, but how did you get better error messages?

error-chain is usually pretty good in that you can chain a low level error like "file not found" with a more high-level message like "unable to load the configuration file, /foo/bar/baz.conf". That way you get an error message which is actually useful, while also retaining the low level context so you can figure out exactly how to fix something.

One example is where we have a parser for some custom file format at work and if there's an error you can use the -v flag to get a full "backtrace" of where the parsing went wrong.

Error: Couldn't parse the drawing
  Caused By: Parsing error on line 42, column 7
  Caused By: In drawing body
  Caused By: In Polyline
  Caused By: In Flags
  Caused By: Invalid flag (0x0010)

That's a lot more user friendly than the generic "unable to parse file" message you'd usually get.

4 Likes

I also solved this problem by creating wrappers for everything. You can see my code here. I feel like someone should create a crate for this because it's not possible to build a user friendly program without something like this.

2 Likes

The quick-error crate's solution to this is something called "Contexts" and the minimal example that introduces this feature looks awfully similar to your problem.

It looks like this:

use quick_error::ResultExt;

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        File(filename: PathBuf, err: io::Error) {
            context(path: &'a Path, err: io::Error)
                -> (path.to_path_buf(), err)
        }
    }
}

fn openfile(path: &Path) -> Result<(), Error> {
    try!(File::open(path).context(path));

    // If we didn't have context, the line above would be written as;
    //
    // try!(File::open(path)
    //     .map_err(|err| Error::File(path.to_path_buf(), err)));

    Ok(())
}

I like quick-error because its documentation and inner workings were easier to grok for me than error-chain. In return, it also has fewer features (e.g. no fancy backtraces), so your mileage may vary.