Crate define_errors [] [src]

A macro for defining error types.

Update

Since writing define_errors I have discovered more fully featured macros that do a better job and have community support (such as error_chain). I would therefore recommend using one of those over define_errors.

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 successively 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 hold 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.