Safety and Efficiency with Rust

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.

Loading Exercise...

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.


Loading Exercise...

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.


Loading Exercise...

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.


Loading Exercise...

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.


Loading Exercise...

Working with Result

Many of the same methods that apply to Option are also used with Result:

  • unwrap() panics if the result is an Err.
  • unwrap_or(default) returns a default value if the result is an Err.
  • unwrap_or_default() returns the default value of the type if the result is an Err.
  • expect(message) panics with a custom message if the result is an Err.

In addition, there are methods specific to Result:

  • is_ok() returns true if the result is Ok.
  • is_err() returns true if the result is Err.

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.

Loading Exercise...