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 Options: 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-catchin 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()returnstrueif the result isOk.is_err()returnstrueif 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.