Where the panic!() macro is primarily for terminating a program by force due to a completely unworkable error, the Result enum, very similar to the Option enum explained recently, is for handling errors that are anticipated in the execution of a method or function.
A return value of the Result type can be precisely one of two variants:
The primary difference between using the panic!() macro and using the Result enum when error handling in Rust is that while the best that can be done with a panic!() is to simply print a statement explaining the error, unsuccessful Result values can be matched on, caught and mitigated without forcibly terminating the program.
Similarly to the Option enum, the value of a Result variable can be extracted from its enum wrapper with the unwrap() method:
fn result() -> Result<i32, String> {
Ok(5)
}
fn main() {
println!("{}", result().unwrap())
}
An additional similarity is that this implementation of unwrap() expects an Ok(V) with a valid return value V and a successful execution,
and will panic if the Result is an Err() of any kind. As with unwrapping an Option, this should only be used with complete confidence in the behaviour of a function or method.
Result also has the expect() method implemented for it, allowing for the same quick error checking for high-confidence functions and methods that return a Result.
When a Result is defined as the type of a variable or function, however, it needs two types in its definition: the successful return type and the error output type. The Result in the above example returns an integer on success, and a string on failure.
Exercise:
Refactor the following code so that the Result is handled without
a call to panic!():
Hint: A match statement is likely to be useful.
Option and Result are so similar, in fact, that they both have methods that allow one to be converted into the other:
fn main() {
let maybe_ten: Option<f32> = Some(10.56);
let success: Result<i32, String> = Ok(4);
let new_result = maybe_ten.ok_or("fail");
let new_option = success.ok();
println!("{:?} {:?}", new_result, new_option)
}
In the above example, the variable maybe_ten is converted into a successful Result, and the variable success is converted into a successful Option.
The method ok_or() takes ownership of the given Option and returns either an Ok() with the Option's contained value, or an Err() with the value passed into the method, respectively depending on the Option being Some() or None.
The ok() method implemented for Result, meanwhile, takes the given Result and returns an Option on the type of the Result's success type.
Another shared piece of functionality between Result and Option is the shorthand Try operator, denoted with a question mark at the end of any function or method call that returns a Result or Option. When this is added to such a statement, it will match on the output, unwrap the value if it is a Some() or Ok() value respectively, and return early with a None or Err() otherwise:
use std::collections::HashMap;
fn option_on_hashmap() -> Option<&'static str> {
let map: HashMap<i32, &'static str> = HashMap::new();
Some(map.get(&5)?)
}
fn main() {
println!("{:?}", option_on_hashmap())
}
Any call of the methods get() or get_key_value() on a hash map returns an Option on the type of the key. In this instance, there's nothing in the hash map, so the attempt to access a value in the hash map fails and the Try operator returns early with a None value, ignoring the Some() wrapper.
Note the syntax around the definition of string slices in the above example. This is the main way to specify string slices as the explicit type of any variable or function, and will be explained in much more detail in the last chapter.