Flashcard Rust: Result Type in Rust and how to handle it
Rust Result
is a type used to return / propogate errors from a function to the caller. It is an Enum
with two variants - an Ok(T)
and an Err(E)
. An Ok(T)
represents success and error represents failure.
In code it looks like:
enum Result<T, E> {
Ok(T),
Err(E),
}
Let’s use a simple program to see how Result type can be used and handled. The program below has two functions, an is_even
and the main
function:
// a simple program that shows how to use Result Type in Rust
use std::io::stdin;
// check if a number is even or odd
// return a Result type (Ok(String) if even, Err(String) if odd)
fn is_even(n: u32) -> Result<String, String> {
let even = n % 2;
match even {
0 => Ok(format!("{} is even!", n)),
=> Err(format!("{} is not even!", n)),
_ }
}
fn main() {
// read input from stdin
let mut input = String::new();
println!("enter an integer: ");
.read_line(&mut input).expect("enter an integer!");
stdin()
// parse String as u32, returns a Result type
let input = input.trim().parse::<u32>().unwrap();
// is_even function returns a custom Result type
let res = is_even(input).unwrap();
println!("{:?}", res);
}
As the name suggests, is_even
is used to check if a digit is even or odd, it returns a Result Type, both Ok
and Err
variants of the type returns a String in case the digit is even or failure (Error) if its odd.
Let’s see how the program works, building and running the program using cargo
:
&& cargo run
➜ results git:(master) ✗ cargo build
enter an integer:2
"2 is even!"
➜ results git:(master) ✗
That worked as expected, we gave 2
and it printed on the screen 2 is even
.
Now if we give an odd number, let’s see what happens:
&& cargo run
➜ results git:(master) ✗ cargo build
enter an integer:1
'main' panicked at 'called `Result::unwrap()` on an `Err` value: "1 is not even!"', src/main.rs:24:30
thread with `RUST_BACKTRACE=1` environment variable to display a backtrace note: run
The program panicked with an Error value, "1 is not even!"
. That is not great, error handling to say the least.
The main
function has a few other things going on as well, the line:
.read_line(&mut input).expect("enter an integer"); stdin()
tries to read a line of input from standard input as a String owned by the variable input
, if it fails, the program panics and outputs, “enter an integer”.
The line:
let input = input.trim().parse::<u32>().expect("error in parsing input");
trims the input string and tries to parse the value as an unsigned integer, it uses the same variable input
to assign its result, this is called shadowing
in Rust, if you are not familar with it, please see
Now that you understand the program, let’s talk about the ways to handle a Result type. There are three ways in general to handle a Result type:
unwrap
- This is the simplest case, here it essentially means, we don’t care about the error, and tells the program to try to get the success (Ok
) value and if the call results in a failure (Err
), panic. This is okay in the case we are writing simple scripts, or knows for sure that there should be anOk
value, or if we, you know are lazy.?
- It is a short hand notation in Rust, which basically tries to unwrap a value if it’s a success (Ok
) or if it’s a failure returns anErr
. As it can have two possible variants, anderrors
have to be handled some way,?
can only be used inside functions that returns a Result Type.
We could use it in our program, but we will have to change the signature of our main
to:
fn main() -> Result(<(), std:io:Error> {
//same code till `let res = ` as original program
let res = is_even(input)?; // change unwrap to ?
Ok(())
}
This can be read as if failure, main will exit with an error code, if not main returns nothing a ()
.
- Finally, we come to the most exhaustive way to handle Result type, here, we use a
match
express for both success (Ok
) and failure (Err
), to capture and handle all possible scenarios gracefully.
To use it in our original program, remove is_even(input).unwrap();
line and add:
fn main() {
//same code till `let res = ` as original program
match is_even(input) {
Ok(val) => println!("{:?}, val),
Err(err) => println!("{:?}, err),
}
}
Doing this, we can avoid our program from panicking and now when we run the program, it doesn’t panic:
&& cargo run
➜ results git:(master) ✗ cargo build
enter an integer:1
"1 is not even!"
&& cargo run
➜ results git:(master) ✗ cargo build
enter an integer:2
"2 is even!"
➜ results git:(master) ✗
yay!, now isn’t this much better than before :) .
So to wrap up, Result type in Rust is an Enum used to handle success and failure scenarios in functions. It can be handled in three ways, using unwrap
, using a ?
or using a match
expression.
References
For more details, please read:
Rust docs on Result type
Unwrap and Expect in Rust
Unrwap and Expect
The end
In this post, we got a basic idea of what Results are and how to handle them. Next time, we can see how to handle errors in a bit more detail.