Preamble
Error handling in Rust is something that I really struggled with initially- coming from C#, where try catch
was readily available and worked well for most situations where an error may be anticipated, not being able to to that was a bit jarring. However, I managed to get a handle on it eventually, and realised that pattern matching Result<>
and Option<>
wasn't that difficult, and in a way is kind of similar to try catch
. And that's what this post is about!
Pattern Matching
First, let's talk about pattern matching, as it's my preferred way of handling errors. When you find a function or variable in Rust, and you want to run different code depending on the value of the variable/return value of the function, you can use Rust's powerful pattern matching system. For example:
fn main() {
let example_variable = "A Different Possible Outcome";
match example_variable {
"First Possible Outcome" => {
println!("The First Outcome is Correct");
}
"Second Possible Outcome" => {
println!("The Second Outcome is Correct");
}
&_ => {
println!("Neither Outcome is Correct");
}
}
}
In this example, the result of running this code would be Neither Outcome is Correct
. As the &_
arm covers any outcome that isn't "First Possible Outcome" or "Second Possible Outcome".
Using Pattern Matching for Error Handling
But how does this apply to error handling? Well, let's take a look at Result<>
and Option<>
, two return value's that you are likely to encounter early on in Rust.
Result<>
When a function returns Result<>
, that means that it the compiler isn't sure whether the function will or will not return an error at runtime, however with pattern matching we can anticipate either scenario. For example:
fn main() {
match function_that_returns_result() {
Ok(_) => {
println!("The function was successful! :]");
}
Err(_) => {
println!("The function was not successful! :[");
}
}
}
In this example, if the function is successful, The function was successful! :]
will be printed to the terminal. If it is not successful The function was not successful! :[
will be printed instead. However, you might be wondering why we put the underscore _
in Ok()
- this is because in Rust, as you may already know, an underscore is often used to define a value that will not be used, as is the case in the above function. But what if we do want the use that value? Well, in that case we can give the value a name that does not start with an underscore, and use it in the corresponding arm.
fn main() {
match function_that_returns_result() {
Ok(ok_value) => {
println!("The function was successful! :]");
println!("{}", ok_value);
}
Err(err_value) => {
println!("The function was not successful! :[");
println!("{}", err_value);
}
}
}
Option<>
When it comes to Option<>
, it's quite similar to Result<>
, with the main difference being that Option<>
is usually used when the compiler doesn't know if a value will exist or not. When it comes to handling Option<>
functions, we can do it in much the same way as with Result<>
, with the only real difference being that instead of Ok()
and Err()
, we use Some()
and None
fn main() {
match function_that_returns_option() {
Some(_) => {
println!("The function returned something! :]");
}
None => {
println!("The function returned nothing! :[");
}
}
}
And just like with Result<>
, we can pass a value through- however with Option<>
you can only do that for the Some()
arm, not the None
arm.
fn main() {
match function_that_returns_option() {
Some(some_value) => {
println!("The function returned something! :]");
println!("{}", some_value);
}
None => {
println!("The function returned nothing! :[");
}
}
}
Conclusion
And that brings us to the end of this post, however I should mention that there are likely may more ways of handling Result<>
and Option<>
, this is just the way I like to do it. Experiment, and see what works for you!