Note: Option values may be created and pattern matched. Most sugar is not working.
The Option type is a built-in union. It is useful when we want to express the idea that we may or may not have a value of a certain type. In some languages, these are sometimes called nullable types, since null represents the absence of a value. Optional values are so useful that many kinds of syntactic sugar exist to make them easy to work with.
Creating Option Values
Let's declare and initialize a variable that might hold an integer:
mut maybeInt ?i32 = 5
Notice that the type specifies a question mark before the integer type. The question mark signifies this is an Option type. It is a syntactic abbreviation for Option[i32].
The value of 5 is automatically coerced into its optional integer equivalent. It would also be possible to specify this explicitly using Some[i32] or its shorter inferred version: Some.
To initialize this variable as having no value, use None:
mut maybeInt ?i32 = None
Again, None is an inferred abbreviation for None[i32]. The abbreviation is only possible when the context makes it clear what sort of Option type we are working with. Thus, this is ambiguous and therefore an error:
mut maybeInt = None // **ERROR**: What type does the Option wrap?
Option References Option references are optimized so they take no more space than regular references. The None reference value is just a non-addressable address (typically, 0).
Unwrapping Option Values
We cannot work with an Option value in the same way we would a value of its enclosed type. Imagine trying to add some number to a optional integer (for example: maybeInt + 4). How should it perform the addition when maybeInt is None? In the face of this uncertainty, the compiler views it as unsafe and will emit an error.
To safely work with a optional value, it must first be unwrapped. This means performing pattern matching on the optional value to determine whether it holds a value or None. Based on which it is, we can then act one way in one case and another way otherwise.
Explicit pattern matching is versatile enough to handle any unwrap-and-act scenario, but the resulting code may be undesirably verbose. Several operators offer succinct ways to work with optional values.
Force unwrap, treat None as an exception
One approach is to expect the value to not be None, and treat it as an exception if it is. To handle a optional value this way, follow the optional value with the ? operator.
imm val = maybeInt?
If maybeInt is not None, then val will be the unwrapped integer value inside.
An exception will be thrown if maybeInt is None. This exception can be caught and handled if a catch None handler is provided. If one is not specified, the exception will panic.
Give None a default value
An alternative approach to unwrapping involves specifying a default value use when the optional value is None. This form of unwrapping uses the or operator:
imm val = (maybeInt or 0) * scale
val will use the value of maybeInt if it is not None. Otherwise it will use 0.
The value to the right of the operator cannot be a optional type, and its type must match that of the wrapped type for the optional value on the left. The expression for the default value will only be evaluated when the optional value is None.
None propagation on safe method calls
A third operator applies when we want to perform some operation on a optional value when it is not None, but evaluate to None when it is. This approach uses the ?. operator, a variation of the dot operator used to perform methods or access fields:
imm x = maybePoint?.dist()
If maybePoint is not None, then x is a optional value that wraps the calculated distance of that point. However, if maybePoint is None, then x will also be None. In effect, we are performing a method or accessing a field if it is safe to do so, and propagating None otherwise. Unlike the previous two operators, the result of using ?. is always a optional value.
This operator may also appear on the left hand side of an assignment statement, and will bypass the assignment altogether if the optional value is None.
Conditional Variable Specialization
Suppose we have a optional value stored in a variable, and we just want to check whether it is not None only once. If it is not None, we want to be able to work with its unwrapped value using the same variable name without having to constantly reaffirm it is not None. This is surprisingly easy to accomplish:
if maybePoint: imm x = maybePoint.x imm y = maybePoint.y imm dist = maybePoint.dist
When an optional value is used in a conditional expression, it evaluates as false if None and true otherwise. So, the above block will be performed whenever maybePoint is not None. When this not None check is using a variable, as it is in this case, a local immutable shadow of that variable is implicitly created at the top of the block which contains a copy of the unwrapped value inside maybePoint. Since this shadow variable contains an unwrapped value, it can be directly used within that block as a Point value. The use of this technique means, however, that the original variable is not accessible nor mutable within the block.
As an extension of this principle, if the conditional expression follows such a check with additional conditions after the and operator, the shadow variable may also be used in the remaining condition:
if maybeInt and maybeInt < 10: scale *= maybeInt
A conditional can also check for the None value:
if !maybeRef: // do some error recovery actions
The block is performed when maybeRef is None. No shadow variable is created for maybeRef at the top of the block, as we have no value to unwrap.
If none of the above convenient approaches apply to your situation, just make use of straightforward pattern matching:
if imm integer = Some[maybeInt]: scale *= integer else: // Some recovery stuff
When maybeInt is not None, its value is unwrapped and copied into the variable named integer, where it can then be used inside that block. If it is None, then the alternative block is performed.