Since Rust does not have exceptions, it does not support exception handling in the traditional sense.
It does provide a macro
!panic
, which can be used to exit out of the program prematurely but it is recommended to be used only for errors that are truly unexpected.
For everything else, Rust uses the following 2 enums:
Option
Result
The structure of the enums is as follows:
// An output can have either Some value or None value.
enum Option<T> { // T is a generic and it can contain any type of value.
Some(T),
None,
}
// A result can represent either Ok or Err.
enum Result<T, E> { // T and E are generics. T can contain any type of value, E can be any error.
Ok(T),
Err(E),
}
Option can either return Some(type)
value or None
.
Result can either return Ok(value)
or Err(any error)
.
If you're wondering why there are 2 constructs for the same purpose, the difference between them is that in
Result
, you can explicitly specify the error message.
The following code snippet is an example of using the Option
enum: (Result
can also be used in a similar manner.)
fn main() {
println!("{:?}", api_call())
}
// This function will either return None or str.
fn api_call() -> Option<&'static str> {
let result = executing_impure_function();
// The `match` is similar to `switch case` in other languages.
match result {
Some(obj) => println!("{:?}", obj),
None => println!("Failed.")
}
return result
}
fn executing_impure_function() -> Option<&'static str> {
//if the optional value is not empty
if true {
return Some("success.");
}
//else
None
}
------------------------------------------------------------------------------
// Output if executing_impure_function() uses `true` in if condition.
"success."
Some("success.")
// Output if executing_impure_function() uses `false` in if condition.
Failed.
None
The ?
operator
This operator is used as a suffix to a function that will return either a Result
or an Option
.
If the function returns Some(value)
or Ok(value)
, then the value is used as the return value.
But if the function returns None
or Err
, the value is immediately returned to the caller of the function. In this case, any code which follows this function call will not be executed.
To better understand this, let us take the code above and tweak it to use the ?
operator.
fn main() {
println!("{:?}", api_call())
}
fn api_call() -> Option<&'static str> {
let result = executing_impure_function()?; // if None, returns immidiately;
println!("printing inside the api_call() function: {:?}", result);
return Some("lorem ipsum")
}
fn executing_impure_function() -> Option<&'static str> {
//if the optional value is not empty
if true {
return Some("success.");
}
//else
return None
}
------------------------------------------------------------------------------
// Output if executing_impure_function() uses `true` in if condition.
printing inside the api_call() function: "success."
Some("lorem ipsum")
// Output if executing_impure_function() uses `false` in if condition.
None
You can see that in the scenario where executing_impure_function
returns None, The print statement inside the api_call
function doesn't get executed and None is immediately returned to the caller.
Whereas in the event executing_impure_function returns a non-None value, the variable result
gets assigned the returned value and the rest of the function is executed.
This is very useful to know since you can suffix potentially unsafe function calls with
?
, which would caution future developers about it.