Stuck on error chain


#1

I have this struct:

#[derive(Debug)]
struct Reply {
    filename: String,
    reply: String,
}

And lots of lines that produce various errors:

fn run() -> Result<Reply> {
    let filename = env::args().skip(1).next().chain_err(
        || Reply::error("no query specified"))?;
    let mut data = String::new();
    let mut file = File::open(filename).chain_err(
        || Reply::error("failed to open query"))?;
    file.read_to_string(&mut data).chain_err(
        || Reply::error("failed to read query"))?;
    let query: Query = serde_json::from_str(&data).chain_err(
        || Reply::error("invalid query"))?;
//...

But this produces errors like this:

error[E0277]: the trait bound `errors::ErrorKind: std::convert::From<Reply>` is not satisfied
  --> myapp\src\main.rs:62:47
   |
62 |     let filename = env::args().skip(1).next().chain_err(
   |                                               ^^^^^^^^^ the trait `std::convert::From<Reply>` is not
mplemented for `errors::ErrorKind`
   |
   = help: the following implementations were found:
             <errors::ErrorKind as std::convert::From<&'a str>>
             <errors::ErrorKind as std::convert::From<std::string::String>>
             <errors::ErrorKind as std::convert::From<errors::Error>>
   = note: required because of the requirements on the impl of `std::convert::Into<errors::ErrorKind>` for
`Reply`

I can see it is telling me to provide a conversion, but how do I do that? And how do I integrate it with error chain?


#2

Hi Mark,

if it is still an option to you I would switch to https://github.com/rust-lang-nursery/failure
It is a much nicer error handling library and it is simpler than error_chain :wink:

If you want to stick with error_chain you have to implement a From Trait for Reply.

regards,
Dominik


#3

I think you’re going about this a bit incorrectly. IIUC, Reply should only serve the purpose of representing a successful request/operation; any error along the way should be encapsulated into the Error type you define using error_chain. If you then want to add contextual information to the chain, such as the file name it failed to open, you can chain_err a message on top; that will create a top level error with the message, and the underlying std::io::Error will be its cause. For example:


#[derive(Debug)]
struct Reply {
    filename: String,
}

error_chain! {
    foreign_links {
        Io(std::io::Error);
        Json(serde_json::error::Error);
    }

    errors {
        UnspecifiedQuery {
            description("No query specified")
            display("No query specified")
        }
    }
}

fn run() -> Result<Reply> {
    let filename = env::args()
        .skip(1)
        .next()
        .ok_or_else(|| ErrorKind::UnspecifiedQuery)?;
    let mut data = String::new();
    // add `filename` as a contextual bit of info
    let mut file = File::open(&filename).chain_err(|| format!("Failed to open file {}", filename))?;
    file.read_to_string(&mut data)
        .chain_err(|| "failed to read query")?;
    let query: Query = serde_json::from_str(&data)?;
    Ok(Reply {
        filename: "blah".into(),
    })
}

How you define the chain also depends on how callers of run() will process errors. For instance, you may want to do the following instead (all else the same):

error_chain! {
    ...

    errors {
       ...        
        FileOpen(file: String) {
            description("Failed to open file")
            display("Failed to open file: {}", file)
        }
    }
}

fn run() -> Result<Reply> {
   ...
   let mut file = File::open(&filename).chain_err(|| ErrorKind::FileOpen(filename.into()))?;
   ...
}

This chains a concrete top level error you’ve defined, and puts the std:io::Error as the cause. This makes it easier to match for FileOpen error kind in the caller, rather than having a generic error when chaining a string.


#4

Thank you, that’s exactly the example I needed.