Errors, Option, and Result
Learning Objectives
- You know how to panic and how to use debug printing.
- You know of the Option and Result types and you know how to use them.
Learning programming is a path paved with errors, and Rust provides no exception to this. Fortunately, when compared to e.g. C, Rust provides a structured way to handle errors. For unrecoverable errors, you can make your program panic (immediately stop execution), while for recoverable errors — those you can gracefully handle — Rust provides Option
and Result
types.
Panicking
Panicking allows stopping the program execution in a controlled manner. The panic!
macro takes a string as an argument, prints the given message, and stops the program.
The “controlled manner” refers to the macro also freeing up the resources reserved by the program before stopping the execution.
Representing “No Value” with Option
In Rust, there is no null
(similar to Gleam). Instead, Rust has the Option
enum to represent the possibility of an absent value. An Option<T>
can be either Some(T)
(meaning a value is present) or None
(meaning no value is present).
The following example demonstrates how to use Option
when converting an integer to its Roman numeral representation.
The function returns an Option<String>
, indicating that the function might fail (i.e., return None
). When converting the integer, the function uses the unwrap_or_default()
method to handle the None
values, returning a default value for the given type — an empty string for a String
in the example’s case.
Unwrap options
There are three methods for unwrapping Option
s: unwrap
, unwrap_or_default
, and unwrap_or
. The unwrap
method returns the value if it is Some
, or panics if it is None
. The unwrap_or_default
method returns a default value if the Option
is None
. The unwrap_or
method returns the value if it is Some
, or a given default value if it is None
.
Another useful method is expect
, which causes panic like unwrap
, but also allows us to specify a custom error message to be shown after the panic.
Debug printing
Some Rust types do not implement the standard printing traits (think of toString
e.g. in Dart), but do support debug formatting. You can use {:?}
with println (or dbg!
) to view the value in a debug format.
The dbg!
macro is more powerful for debugging. It prints the filename, line number, debugged expression, and returns the argument that allows debugging parts of the expressions.
None and explicit typing
Rust requires explicit typing for None
. The compiler cannot infer the type of None
, and just using None
as a value for a variable leads to a compilation error.
Rust is able to hint that None
is of type Option<T>
, but it also needs to know what it is an option of — that’s the <T>
part in the type. To fix the error, we need to define the data type of the variable in the code as follows.
Destructuring options
Match expressions can be used to destructure options. The following example demonstrates how to use the match
expression to destructure an Option
and print the value if it is Some
, or otherwise print a default message.
Another possibility is to use if let
syntax, which takes the form if let $pattern = $expression
. If the lefthand pattern matches the righthand expression, Rust can safely unwrap the expression some_value
into value
. The same logic works for all enums in Rust.
Note that if let
requires the lefthand and righthand sides to be the same type, i.e., the following would not work.
Result and recoverable errors
For recoverable errors — cases where you might want to propagate an error message, or try an alternative approach, Rust has the Result
type. The Result
type is an enum with two variants: Ok
and Err
. The Ok
variant wraps a value, while the Err
variant wraps an error message.
There is no
try-catch
in Rust.
The Result type
A Result<T, E>
can be either Ok(T)
or Err(E)
, where T
is the type of the value and E
is the type of the error. The following example demonstrates how to use Result
when parsing a string into a number.
The compiler needs to always know the types of both Ok
and Err
variants.
Working with Result
Many of the same methods that apply to Option
are also used with Result
:
unwrap()
panics if the result is anErr
.unwrap_or(default)
returns a default value if the result is anErr
.unwrap_or_default()
returns the default value of the type if the result is anErr
.expect(message)
panics with a custom message if the result is anErr
.
In addition, there are methods specific to Result
:
is_ok()
returnstrue
if the result isOk
.is_err()
returnstrue
if the result isErr
.
The ehaustive list can be found in Rust’s documentation.
Matching and Result
As with Option
, we can use the match
expression to pattern match the Ok
and Err
variants and destructure the values inside. Likewise, the if let
expression can be used to handle the Ok
and Err
variants.
Returning a Result
from a function requires both Ok
and Err
to be explicitly defined. Below is an example in a program that mimics (poorly) a general artificial intelligence.