Macro tear::terror[][src]

macro_rules! terror {
    ( $e:expr ) => { ... };
    ( $e:expr => $f:expr ) => { ... };
}

try!-like error-handling macro

terror! is like tear!, but stronger and more righteous. It automatically converts the Bad value to the return type Bad value (Judge trait).

Description

let x = terror! { $e };

If $e is a good value, it is assigned to x. Otherwise, $e is Bad(value), we return from_bad(value). This form is equivalent to the ? operator.

let x = terror! { $e => $f };

Same as the previous form, but the bad value is first mapped through $f before returning. In short, we return from_bad($f(value)).

Both forms make use of the convert::From trait to convert the bad value, making it fully compatible with try! and the ? operator.

Explanation using examples

The description is especially terse on purpose: it is really hard to explain what terror! does without using examples.

Simple examples

Ripping Good and Bad values

even_number is assigned 2 because Good(2) is Good.

fn return_two() -> Result<i32, String> {
    let even_number: i32 = terror! { Good::<i32, String>(2) };
}

error_five returns early with Err("five".to_string()) because Bad("five".to_string()) is Bad.

fn error_five() -> Result<i32, String> {
    let another_number: i32 = terror! { Bad("five".to_string()) };
}

Handling errors

Forwarding errors: If s.into_string is Ok(v), the String v is assigned to s. If it is Err(e) with e being an OsString, we return Err(e).

fn len(s: OsString) -> Result<usize, OsString> {
    //        ┌─────────────────┐         │
    //        │        Result<String, OsString>
    //        │         └───────────┐
    let s: String = terror! { s.into_string() };

    Ok(s.len())
}

Using a mapping function: we converts the error to the return type error

fn to_string(b: Vec<u8>) -> Result<String, String> {
    let s = terror! { String::from_utf8(b) => |e: FromUtf8Error| e.utf8_error().to_string() };

    Ok(s)
}

The first form: terror! { $e }

fn parse_number (s :String) -> Result<i64, ParseIntError> {
    // Early return on error
    let n: i32 = terror! { s.parse() };
    Ok(n as i64)
}

In this example, s.parse() returns a Result<i32, ParseIntError>. The good value is i32, and the bad value is ParseIntError.

If we parsed the string succesfully, terror! evaluates to the parsed i32 and it is assigned to n.

But if fails, the ParseIntError is returned as an error. This means that our Err::<i32, ParseIntError> is converted to a Err::<i64, ParseIntError> and then returned.

This form of terror! is especially useful when you just want to forward the error from a function call to the function return value. Exactly like the ? operator.

The second form: terror! { $e => $f }

enum Error {
    Parse(ParseIntError),
    Io(io::Error),
}

fn square (s: String) -> Result<String, Error> {
    // If parse_number fails, convert the ParseIntError into our Error type and return early
    let number: i64 = terror! { parse_number(s) => Error::Parse };
    
    // Square the number and convert it to string
    let squared = (number * number).to_string();
    Ok(squared)
}

We now know that parse_number returns a Result<i64, ParseIntError>.

We would now like to wrap that ParseIntError error into our our custom Error error type. To do so, we extract the ParseIntError, and wrap it into our custom error with Error::Parse.

That is the role of the function following the => arrow: it converts the error type of the left statement, into the function return error type.

Automatic conversion just like ?

Since terror! mimics ?, it also supports autoconversion using the convert::From trait.

enum CustomError {
    IOError(io::Error),
    OtherError,
}

impl std::convert::From<io::Error> for CustomError {
    fn from(x :io::Error) -> Self {
        CustomError::IOError(x)
    }
}

fn auto_convert() -> Result<bool, CustomError> {
    terror! { fail_with_io_error() };
    Ok(false)
}

assert_match![ auto_convert(), Err(CustomError::IOError(_)) ];

terror! vs. ? when moving into closures

The only difference between terror! and ? is that since terror! is a macro, you can move variables into the closure without the borrow checker yelling at you.

In this example, we want to return an error built from path using the ? operator.

fn open_file(path: PathBuf) -> Result<(), Error> {
    let file = File::open(&path).map_err(|_| Error::OpenF(path))?;
    
    // Do stuff with path and file
}

However, it fails to compile with the message error[E0382]: use of moved value: `path`. This is because the borrow checker can’t tell that when the closure is called, it immediately returns. It sees that path is moved into the closure, and refuses to let you use it in the rest of the function.

But if works if we use terror!. That’s because since it’s a macro, it expands into code that tells the compiler that we immediately return after calling the closure.

fn open_file(path: PathBuf) -> Result<(), Error> {
    let file = terror! { File::open(&path) => |_| Error::OpenF(path) };
    
    // Do stuff with path and file
}

Naming

The name terror comes from “return error” and “tear! error”. The mnemonic was “When you need to scream an error from the inside” because of how closures worked (see §terror! vs. ? when moving into closures).