Working With Rust Result - Asides - Part 13
Here are some general guidelines to keep in mind.
Functions that return Result in std
Many of the std
functions return Result
s if the action you’re trying to perform can fail. Here’s
an example from the Write trait in std::io
:
trait Write {
fn write_all(&mut self, buf: &[u8]) -> Result<()>
...
}
Using the write_all
method to write the contents of the supplied buffer (buf
) can fail with IO errors, or it could succeed with a Unit (()
).
Result
should have two type variables and the example above clearly only has one. What’s going on?
A frequent pattern used in Rust libraries is to create a type alias for Result
that wraps a particular error type. In the example above, Result
is aliased as follows:
pub type Result<T> = Result<T, Error>;
Where Error
is a std::io::Error:
pub struct Error { /* private fields */ }
Essentially giving you:
pub type Result<T> = Result<T, std::io::Error>;
With a type alias like above, we don’t have to constantly specify a type for a Result
’s error. This is useful where many methods return the same error type. For example, all std::io
methods that return Result
use std::io::Error
as the error type.
Eager vs Laziness
There’s a distinction that applies to all variants for Result
methods that take in a value
vs a function
. Let’s take unwrap_or
and unwrap_or_else
as an example. As a refresher, here are the definitions for both functions.
unwrap_or
:
pub fn unwrap_or(self, default: T) -> T {
match self {
Ok(t) => t,
Err(_) => default,
}
}
In the above, default
is a value.
unwrap_or_else
:
pub fn unwrap_or_else<F: FnOnce(E) -> T>(self, op: F) -> T {
match self {
Ok(t) => t,
Err(e) => op(e),
}
}
In the above, op
is a function.
With unwrap_or_else
, the function supplied (op
) will not get called unless there is an Err
value to call it with; it will not get called on Ok
values. This is different to unwrap_or
’s default value which is evaluated on Ok
values as well:
let strict_result_ok: Result<u32, String> = Ok(1);
let strict_result_err: Result<u32, String> = Err("You have errors".to_owned());
.unwrap_or(panic!("boom")); // panics even though this is an `Ok`
strict_result_ok.unwrap_or_else(|_| panic!("boom")); // does not panic because this is an `Ok`
strict_result_ok.unwrap_or_else(|_| panic!("boom")); // panics on `Err` as expected strict_result_err
You can think of unwrap_or
as being “eager” in its evaluation of the default
parameter - it always evaluates the default value on Ok
and Err
. The op
function in unwrap_or_else
can be thought of as “lazy” or “evaluated when needed” - it only runs when the value returned is an Err
(as functions can only get called with their input parameters). Generally, only use constants or precomputed values with methods that are eager to prevent unnecessary evaluation.