Crate define_errors [−] [src]
A macro for defining error types.
Introduction
The define_errors
macro defines new error types. The error types
are enums that can hold other error values and/or error
descriptions. The macro is designed to allow the adding of
multiple levels of context to errors by sucessively wrapping
them, without losing any of the structured information contained
in the wrapped errors. It tries to be as simple as possible while
still achieving that goal.
Plain and wrapped errors
To illustrate the syntax we will use examples. Firstly, the
following code will define a new error type called MyError
, and
a macro myerr_new
that can be used to create new values of type
MyError
:
use std::error::Error; // required use statement define_errors! { MyError myerr_new {} }
Note that the use
statement is required. However, you can use
another name binding if you need to. For example, this will also
work:
use std::error::Error as StdError; define_errors! { MyError myerr_new {} }
If the type being defined needs to be declared public you can add
the pub
keyword before the name. Also, you can define multiple
error types at once (although in most cases one is all that is required):
define_errors! { pub MyError myerr_new {} MyError2 myerr2_new {} MyError3 myerr3_new {} }
The MyError
definition inserted by the macro above looks like
this:
pub enum MyError { Plain { description: String, }, Wrapped { err: Box<MyError>, description: String, }, }
Since MyError
is an enum, MyError
values can be different
variants. In this case: Plain
and Wrapped
. Plain
holds a
simple description string, but Wrapped
holds a description and a
"wrapped" error, also of type MyError
. The two variants can be
created in a simple way using the automatically defined
myerr_new
macro, as shown in the next code example. The
description passed to myerr_new
can be either a &str
or a
String
:
let e_plain = myerr_new!(Plain, "a plain error"); let e_wrapped = myerr_new!(Wrapped, e_plain, "a wrapped error");
When e_wrapped
is converted to a string it will show the
description given to the myerr_new
call, as well as the
description contained inside the wrapped error:
assert_eq!(format!("{}", e_wrapped), "a wrapped error: a plain error");
In other words, e_wrapped
has taken e_plain
and added context
to it. It should also be noted that e_wrapped
takes ownership of
e_plain
.
Formatted descriptions
There is a shortcut for creating formatted descriptions for
Plain
and Wrapped
. Instead of
let e_plain2 = myerr_new!(Plain, format!("{}: {}", "str1", "str2"));
you can instead use
let e_plain3 = myerr_new!(Plain, "{}: {}", "str1", "str2");
This works for up to six format
arguments, and for Plain
and
Wrapped
variants of MyError
.
Existing error types
define_errors
can also define error types that holds existing
error types (such as std::io::Error
) in corresponding
variants. For example:
define_errors! { MyError myerr_new { Io: std::io::Error, Utf8: std::str::Utf8Error, } }
This defines an enum that looks like:
enum MyError { Plain { description: String, }, Wrapped { err: Box<MyError>, description: String, }, Io { err: std::io::Error, }, Utf8 { err: std::str::Utf8Error, }, }
Plain
and Wrapped
variants are still present, but we now also
have Io
and Utf8
. These new variants of MyError
can be
created in a similar fashion to before, again using
myerr_new
. As when creating Wrapped
variants, myerr_new
takes ownership of the errors being passed to it:
// io_err: value of type std::io::Error let e_io = myerr_new!(Io, io_err); // utf8_err: value of type std::str::Utf8Error let e_utf8 = myerr_new!(Utf8, utf8_err);
A nice thing is that the From
traits for existing error types
are implemented as standard by define_errors
. In this case:
From<std::io::Error> for MyError From<std::str::Utf8Error> for MyError
This means that when using the question mark operator or try
macro, existing error types get converted to our MyError
type
automatically:
type MyResult<T> = Result<T, MyError>; fn decode_some_utf8() -> MyResult<()> { // std::str::Utf8Error errors here are converted to MyError std::str::from_utf8(&[255])?; // this line is never reached - &[255] is bad utf8 unreachable!(); } fn main() { match decode_some_utf8() { Ok(_) => unreachable!(), Err(ref e) => { assert_eq!(e.description(), "invalid utf-8: corrupt contents"); } } }
Lastly, MyError
also implements the std::error::Error
trait,
so you can create trait objects if you wish:
fn use_trait_object(err: &std::error::Error) { // do stuff with err let _ = err.description(); let _ = err.cause(); } fn main() { let e = myerr_new!(Plain, "a plain old error"); use_trait_object(&e); }
That is all there is to define_errors
.
A complete example
Here is a complete example illustrating most of the above:
#[macro_use] extern crate define_errors; use std::error::Error; use std::fs::File; use std::io::Read; use std::path::Path; define_errors! { MyError myerr_new { Io: std::io::Error, } } type MyResult<T> = Result<T, MyError>; fn load_file(path: &Path) -> MyResult<Vec<u8>> { // std::io::Error values here are automatically converted to MyError let mut f = File::open(path)?; let mut buffer = Vec::new(); let _ = f.read_to_end(&mut buffer)?; Ok(buffer) } fn load_file_add_context(path: &Path) -> MyResult<Vec<u8>> { if path.starts_with("/forbidden_area") { return Err(myerr_new!(Plain, "{}: no access", path.to_string_lossy())); } load_file(path).map_err(|e| { // add path context to errors returned from load_file myerr_new!(Wrapped, e, path.to_string_lossy()) }) } fn main() { let result = load_file_add_context(Path::new("/path/to/invalid/file")); match result { Ok(_) => unreachable!(), Err(ref e) => { assert_eq!(format!("{}", e), "/path/to/invalid/file: entity not found"); assert_eq!(e.description(), "/path/to/invalid/file: entity not found"); } } }
Macros
define_errors |
A macro for defining error types. |