30 Handling Errors
Be able to match
on a Result<T>
and handle errors when they occur.
Many times when we use a function or a method, the result is fallible. A fallible function is one in which an error may occur. An infallible function will always return a valid value for example fn add2(x: f64, y: f64) -> f64
.
Errors are data
Errors cannot happen quietly in Rust. They must be handled. In Rust, errors are data. When an error is possible a function should return a Result.
A Result
, is a special type of enum that has two variants:
enum Result {
Ok(T),
Err(E)
}
Enums can contain data. In the case of the Result<T, E>
, when a function completes successfully, the variant is called Ok()
. When an error is occurred the variant is called Err()
.
To access the values of the Result<T, E>
, we can match
on the enum variants.
Example
fn main() {
let number_str = "10 0 0";
let number = match number_str.parse::<i32>() {
Ok(number) => println!("The number is {number}"),
Err(e) => println!("We ran into an error! {e:?}"),
};
}
We ran into an error! ParseIntError { kind: InvalidDigit }
UX for Errors
This is a philosophical conversation, really.
I’m of the mind that, for vectorized functions, we do not give up on everything if an error has occured. Instead, return NULL
or NA
wherever an error may have occurred.
Exercise
- Handle errors from
encode()
:- return
Rstr::na()
when there is an error - otherwise, return
Rstr::from(res)
- return
Solution
View hint
Perform a match on the result of encode()
e.g.
let res = encode(coord, length);
match res {
Ok(gh) => todo!(),
Err(_) => todo!()
}
View solution
#[extendr]
fn gh_encode(x: Doubles, y: Doubles, length: usize) -> Strings {
if length == 0 || length > 12 {
"`length` must be between 1 and 12");
throw_r_error(}
.into_iter()
x.zip(y.into_iter())
.map(|(xi, yi)| {
let coord = Coord {
: xi.inner(),
x: yi.inner(),
y};
match encode(coord, length) {
Ok(res) => Rstr::from(res),
Err(_) => Rstr::na()
}
})
.collect::<Strings>()
}