Use the Error
type
This pattern is a way to manage errors when you have multiple kinds of failure that could occur during a single function. It has several distinct advantages:
- You can start using it without defining any of your own failure types.
- All types that implement
Fail
can be thrown into theError
type using the?
operator. - As you start adding new dependencies with their own failure types, you can start throwing them without making a breaking change.
To use this pattern, all you need to do is return Result<_, Error>
from your
functions:
# #![allow(unused_variables)] #fn main() { use std::io; use std::io::BufRead; use failure::Error; use failure::err_msg; fn my_function() -> Result<(), Error> { let stdin = io::stdin(); for line in stdin.lock().lines() { let line = line?; if line.chars().all(|c| c.is_whitespace()) { break } if !line.starts_with("$") { return Err(format_err!("Input did not begin with `$`")); } println!("{}", &line[1..]); } Ok(()) } #}
When might you use this pattern?
This pattern is very effective when you know you will usually not need to destructure the error this function returns. For example:
- When prototyping.
- When you know you are going to log this error, or display it to the user, either all of the time or nearly all of the time.
- When it would be impractical for this API to report more custom context for the error (e.g. because it is a trait that doesn't want to add a new Error associated type).
Caveats on this pattern
There are two primary downsides to this pattern:
- The
Error
type allocates. There are cases where this would be too expensive. In those cases you should use a custom failure. - You cannot recover more information about this error without downcasting. If your API needs to express more contextual information about the error, use the Error and ErrorKind pattern.