[][src]Macro tear::rip

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

try!-like error-handling macro

rip! 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 = rip! { $e };

If $e is a good value, it is assigned to x. Otherwise, $e is Bad(value), we return from_bad(value).

let x = rip! { $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)).

Simple examples

Ripping Good and Bad values.

fn add_even_numbers() -> Result<String, i32> {
    // Assigns 2 to even_number because `Good(2)` is Good
    let even_number: i32 = rip! { Good(2) };
    
    // Returns Err(5) because `Bad(5)` is Bad
    let another_number: i32 = rip! { Bad(5) };
    
    let result = even_number + another_number;
    Ok(result.to_string())
}

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 = rip! { s.into_string() };

    Ok(s.len())
}

Using a mapping function.

fn to_string(b: Vec<u8>) -> Result<String, String> {
    // Converts the error to the return type error
    let s = rip! { String::from_utf8(b) => |e: FromUtf8Error| e.utf8_error().to_string() };
    Ok(s)
}

Explanation using examples

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

The first form: rip! { $e }

fn parse_number (s :String) -> Result<i64, ParseIntError> {
    // Early return on error
    let n: i32 = rip! { 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, rip! 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 rip! 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: rip! { $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 = rip! { 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.

Comparison with ? (try operator)

rip! behaves like the ? operator, with the difference that it doesn't automatically convert the actual error type.

This means that these two statements are generally equivalent:

File::open(&path)?;
rip! { File::open(&path) };

Except when we expect automatic conversion, for example when using types like Result<T, Box<dyn Error+Send+Sync>>. See Error Handling in Rust §The From trait and anyhow, an error handling crate. In which case you would need to explicitly specify the conversion.

use std::convert::From;
rip! { File::open(&path) => From::from };

rip! vs. ? when moving into closures

Since rip! is a macro,

terror! is like rip!, but with a function that maps the bad value. The only difference is that it is a macro, so 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.

This example deliberately fails to compile
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 rip!. 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
}