[−][src]Macro tear::rip
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.
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 }