Rust avoids the billion dollar mistake of including null s in the language. Instead, we can represent a value that might or might not exist with the Option type. This is similar to Java 8 Optional or Haskell’s Maybe . There is plenty of material out there detailing why an Option type is better than null, so I won’t go too much into that.

In Rust, Option<T> is an enum that can either be None (no value present) or Some(x) (some value present). As a newbie, I like to learn through examples, so let’s dive into one.

Example

Consider a struct that represents a person’s full name. The first and last names are mandatory, whereas the middle name may or may not be present. We can represent such a struct like this :

struct FullName { first : String , middle : Option < String > , last : String , }

Let’s create full names with/without a middle name:

let alice = FullName { first : String :: from ( "Alice" ), middle : Some ( String :: from ( "Bob" )), // Alice has a middle name last : String :: from ( "Smith" ) }; let jon = FullName { first : String :: from ( "Jon" ), middle : None , // Jon has no middle name last : String :: from ( "Snow" ) };

Suppose we want to print the middle name if it is present. Let’s start with the simplest method, unwrap() :

println! ( "Alice's middle name is {}" , alice .middle .unwrap ()); // prints Bob

It works! Let’s try it with Jon:

println! ( "Jon's middle name is {}" , jon .middle .unwrap ()); // panics

So, unwrap() panics and exits the program when the Option is empty i.e None . This is less than ideal.

Pattern matching

Since Option is actually just an enum , we can use pattern matching to print the middle name if it is present, or a default message if it is not.

println! ( "Jon's middle name is {}" , match jon .middle { None => "No middle name!" , Some ( x ) => x , } );

This fails compilation with the error:

error[E0308]: match arms have incompatible types --> src/main.rs:28:9 | 28 | / match jon.middle { 29 | | None => "No middle name!", 30 | | Some(x) => x, 31 | | } | |_________^ expected &str, found struct `std::string::String`

Recall in my earlier post, that a string literal is actually a string slice. So our None arm is returning a string slice, but our Some arm is returning the owned String struct member. Turns out we can conveniently use ref in a pattern match to borrow a reference. Again, recalling that &String can be coerced to &str , this solves our type mismatch problem.

println! ( "Jon's middle name is {}" , match jon .middle { None => "No middle name!" , Some ( ref x ) => x , // x is now a string slice } );

This works!

Option methods

Pattern matching is nice, but Option also provides several useful methods. We can achieve what we did in the previous section with unwrap_or() :

println! ( "Alice's middle name is {}" , alice .middle .unwrap_or ( "No middle name!" .to_owned ()));

map

map() is used to transform Option values. For example, we could use map() to print only the middle initial:

println! ( "Alice's full name is {} {} {}" , alice .first , alice .middle .map (| m | & m [ 0 .. 1 ]) .unwrap_or ( "" ), // Extract first letter of middle name if it exists alice .last );

However, this fails to compile with the very clear error:

42 | | alice.middle.map(|m| &m[0..1]).unwrap_or(""), | | - ^ `m` dropped here while still borrowed | | | | | borrow occurs here

Ah, so map() consumes the contained value, which means the value does not live past the scope of the map() call! Luckily, the as_ref() method of Option allows us to borrow a reference to the contained value:

println! ( "Alice's full name is {} {} {}" , alice .first , alice .middle .as_ref () .map (| m | & m [ 0 .. 1 ]) .unwrap_or ( "" ), // as_ref() converts Option<String> to Option<&String> alice .last );

Instead of first using map() to transform to another Option and then unwrapping it, we can use the convenience method map_or() which allows us to do this in one call:

alice .middle .as_ref () .map_or ( "" , | m | & m [ 0 .. 1 ])

and_then

and_then() is another method that allows you to compose Options (equivalent to flatmap in other languages). Suppose we have a function that returns a nickname for a real name, if it knows one. For example, here is such a function (admittedly, one that has a very limited worldview):

fn get_alias ( name : & str ) -> Option <& str > { match name { "Bob" => Some ( "The Builder" ), "Elvis" => Some ( "The King" ), _ => None , } }

Now, to figure out a person’s middle name’s nickname (slightly nonsensical, but bear with me here), we could do:

let optional_nickname = alice .middle .as_ref () .and_then (| m | get_nickname ( & m )); println! ( "Alice's middle name's nickname is {}" , optional_nickname .unwrap_or ( "(none found)" )); // prints "The Builder"

In essence, and_then() takes a closure that returns another Option . If the Option on which and_then() is called is present, then the closure is called with the present value and the returned Option becomes the final result. Otherwise, the final result remains None . As such, in the case of jon , since the middle name is None , the get_nickname() function will not be called at all, and the above will print “(none found)”.

Summary