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