Hello everyone, new episode of rust series is here, today we will discuss error handling in Rust. Proper error handling is key aspect for successful code in any programming language. Rust uses return values for error handling (in spite of languages intensively using exception). Let's start with some basics.
panic!
Panic macro terminates thread and report message. As we are in main thread, program is terminated.
fn main() {
fn negate_one_digit(digit: i32) -> i32 {
if digit < -9 || digit > 9 {
panic!("Invalid argument, number must be between -9..9");
}
digit * (-1)
}
let result = negate_one_digit(10);
println!("{}",result);
}
# output
thread 'main' panicked at 'Invalid argument, number must be between -9..9',
Wrapped result
It's common to "wrap" return values in Rust into something like Option or Result
First check these enums
enum Option<T> {
None,
Some(T),
}
Option enum express possibility of absence (with None).
enum Result<T, E> {
Ok(T),
Err(E),
}
Result enum express possibility of error (with Err(E))
Unwrapping
Unwrapping is getting result if there is some (for Option) or if there is no error (for Result). Otherwise it calls panic!.
Check this for Option
impl<T> Option<T> {
fn unwrap(self) -> T {
match self {
Option::Some(val) => val,
Option::None =>
panic!("called `Option::unwrap()` on a `None` value"),
}
}
}
And this for Result
impl<T, E: ::std::fmt::Debug> Result<T, E> {
fn unwrap(self) -> T {
match self {
Result::Ok(val) => val,
Result::Err(err) =>
panic!("called `Result::unwrap()` on an `Err` value: {:?}", err),
}
}
}
Using Option
fn negate_one_digit(digit: i32) -> Option<i32> {
let res : Option<i32>;
if digit < -9 || digit > 9 {
return None;
}
Some(digit * (-1))
}
fn main() {
let res = negate_one_digit(9).unwrap();
println!("{:?}", res );
let res = negate_one_digit(10).unwrap();
}
# output
-9
error: thread panicked
Using Result
Now we implement similar function with using Result as return value
static CODE_OUT_OF_RANGE: i32 = 1;
fn negate_one_digit(digit: i32) -> Result<i32, i32> {
let res : Result<i32, String>;
if digit < -9 || digit > 9 {
return Err(CODE_OUT_OF_RANGE);
}
Ok(digit * (-1))
}
fn main() {
let res = negate_one_digit(9).unwrap();
println!("{:?}", res );
let res = negate_one_digit(10).unwrap();
}
# output
-9
error: thread panicked
unwrap_or
unwrap() means give me a result or if there is an error or no result then panic.
unwrap_or() means give me a result if there is an error or no result then return some predefined value;
Let's rewrite previous chapter with that
fn main() {
let res = negate_one_digit(9).unwrap_or(0);
println!("{:?}", res );
let res = negate_one_digit(10).unwrap_or(0);
println!("{:?}", res );
}
# output
-9
0
Now if there is a problem we will get neutral value 0.
Avoiding panicking with a better way
Easy approach is to match result and match the result and then perform custom action.
fn evaluate_res(res: Result<i32,i32>) {
match res {
Ok(value) => println!("{}", value),
Err(err) => println!("ERROR_CODE:{}", err)
}
}
fn main() {
let res = negate_one_digit(9);
evaluate_res(res);
let res = negate_one_digit(10);
evaluate_res(res);
}
# output
Error types
You can defined your own error types or codes as in previous piece of code but there are also standard ways like std::error:Error trait.
use std::fmt::{Debug, Display};
trait Error: Debug + Display {
/// A short description of the error.
fn description(&self) -> &str;
/// The lower level cause of this error, if any.
fn cause(&self) -> Option<&Error> { None }
}
There is more to error handling and we will return to this topic in the future but for now it's good to have some basics.
Postfix
That's all for now, thank you for your appreciations, feel free to comment and point out possible mistakes (first 24 hours works the best but any time is fine). May Jesus bless your programming skills, use them wisely and see you next time.
Meanwhile you can also check the official documentation for more details if you wish.