One common monadic structure is the
Maybe in Haskell and other languages) type. This can be seen as an encapsulation type. Consider a function which may fail to produce a meaningful value for certain inputs. For example,
from_str function cannot return a meaningful value for
"Potato". Rust (and many other functional languages) does not have
null, so what should we return? This is where an
Option type becomes useful. In our example, instead of returning an
int type, the
from_str function returns an
In Rust, the
Option enum is represented by either
x is the encapsulated value. In this way, the
Option monad can be thought of like a box. It encapsulates the value
x is any type. Rust defines an
Option as such, where
(T) denote that it handles a generic type, meaning that
T could be an
vec, or anything else, even other
Value In, Value Out
Since we can see an
Option as a box, or encapsulation type, we need to be able to put things into the box, or take things out.
Putting a value into an
Option is delightfully simple. Simply use
None in place of
x or (an imaginary)
null. Most of the time, you will receive or return an
Option based on input, rather than just creating them directly. Here are some examples of different techniques.
To take something out of the
Option we need to be able to extract, or "unwrap" the value. There are a number of ways to do this. Some common methods are with a
If you're seeking to write code that won't crash, avoid
.expect()and it's cousin
.unwrap()and use safer alternatives like
Not Just a Null
By now, you're probably asking yourself something similar to the following:
So why not just have
null? What does an
Optionmonad provide that's more?
That's a very good question. What are the benefits of this paradigm?
- You must handle all possible returns, or lack thereof. The compiler will emit errors if you don't appropriately handle an
Option. You can't just forget to handle the
None(or 'null') case.
- Null doesn't exist. It's immediately apparent to readers and consumers which functions might not return a meaningful value. Attempting to use a value from an
Optionwithout handling it results in a compiler error.
- Values aren't boxed.
Optionvalues don't wrap pointers, they wrap values. In order to have a
null, you necessarily need a pointer. (Thanks cmr!)
- Composition becomes easy. The
Optionmonad becomes much more powerful when it is used in composition, as its characteristics allow for pipelines to be created which don't need to explicitly handle errors at each step.
Let's take a closer look at the composition idea...
Composing a Symphony of Functions
Nirvana is being able to compose a series of functions together without introducing a tight dependency between them, such that they could be moved or changed without needing to be concerned with how this might affect the other functions. For example, let's say we have some functions with the following signatures:
; // This could fail. (log(-2) == ??) ; // This could fail. (sqrt(-2) == ??) ; ; ;
Quite the little math library we have here! How about we come up with a way to turn
20 into something else, using a round-about pipeline?
With our little library it'd look something like this:
// This code will not compile, it's invalid. // `Null` isn't a real type in Rust.
In this case, we had two functions which could fail, since we didn't have an
Option type, the author must be aware of and handle possible
Null values. Note that the onus was on the programmer to know when a
Null might be returned, and remember to handle it, not on the compiler.
Let's see what the same code would look like using the
Option monad. In this example, all of the functions are appropriately defined.
// You can ignore these.
This code handles all possible result branches cleanly, and the author need not explicitly deal with each possible
None result, they only need to handle the end result. If any of the functions which may fail (called by
and_then()) do fail, the rest of the computation is bypassed. Additionally, it makes expressing and understanding the pipeline of computations much easier.
and_then (along with a gamut of other functions listed here) provide a robust set of tools for composing functions together. Let's take a look, their signatures are below.
map provides a way to apply a function of the signature
|T| -> U to an
Option<T>, returning an
Option<U>. This is ideal for functions like
double() which don't return an
This call corresponds to
fmap in Haskell, which is part of a functor. Monads have this trait because every monad is a functor.
and_then allows you to apply a
|T| -> Option<U> function to an
Option<T>, returning an
Option<U>. This allows for functions which may return no value, like
sqrt(), to be applied.
This call corresponds to
bind in Haskell and theoretical Monad definitions. Meanwhile unwrapping
None is the equivalent of
return. (Thanks to dirkt)
Working with Options in Vectors. Parsing a vector of strings into integers. Note that Rust's iterators are lazy, so if
collect() isn't called, the iterator itself could be composed with others.
A simple pipeline. This example takes a strong and splits it into an iterator.
next() fetches the next token, which is an