Optionals

An optional T? holds either a present value of type T or nothing — absence, written none. It is a compiler-owned type: no package declares it, it has no methods, and the compiler makes sure absence is dealt with before the value inside is used.

optional-type = type "?"

The type

? is a postfix type suffix. Like [], [N] and [..], it binds tighter than every other type operator, so it applies to the type immediately before it. Group with parentheses to put it anywhere else:

TypeMeaning
int32?an optional int32
int32??an optional of an optional int32 — two levels
int32?[..]a slice of optional int32 values
int32[..]?an optional slice
*int32?a pointer to an optional, *(int32?)
(*int32)?an optional pointer
(int32 | bool)?an optional sum
int32 | (bool?)a sum with an optional member
(T ! E)?an optional fallible
T ! (E?)a fallible whose error is optional

A ? never reaches across | or !, so the last two pairs must be grouped. Without the parentheses the compiler stops:

error: an optional sum member must be grouped
  help: write 'A | (B?)' for an optional member, or '(A | B)?' for an optional sum
error: an optional error type must be grouped
  help: write 'T ! (E?)' for an optional error, or '(T ! E)?' for an optional result

Any type can be the payload: a struct, a slice, a pointer, a fallible, the unit () (a ()? is just "present or not"), and another optional.

Levels never collapse

T?? is not T?. Each level has its own absence, so an int32?? has three distinct values:

ValueMeaning
noneabsent
.Some(none)present, holding an absent int32?
.Some(.Some(20)), or 20present, holding a present 20

This is what lets a lookup tell "there is no such key" from "the key is there, and its value is absent", and an iterator tell "there are no more items" from "the next item is an absent value".

Writing an optional value

There are three ways to produce an optional, and every nested state can be written directly, without a temporary:

let count: int32? = 4;      // a plain value where an optional is expected is present
let same: int32? = .Some(4); // presence written out
let missing: int32? = none;  // absence
let inferred = .Some(4i32);  // .Some alone takes its payload's type: int32?
let wrapped: int32?? = count; // present, holding count: .Some(.Some(4))
  • A plain value. Where an optional is expected — an annotated let, an assignment, an argument, a return in a function returning T? — a value of type T is made present. The same happens one level up: an int32? placed where an int32?? is expected becomes .Some of it.
  • .Some(value) selects presence explicitly. It is required where a plain value would be ambiguous, and it is how a present absence, .Some(none), is written.
  • none is the absence of the outer level of the optional the context expects, never of a level inside it. In a function returning int32? ! E (a fallible whose success is optional), return none; succeeds with an absent value.

none carries no type of its own, so it needs a context:

error: cannot infer the type of 'absent' from 'none'
  help: annotate the optional type, as in 'let value: int32? = none;'

An optional payload can also widen: an A? converts to (A | B)?, keeping absence absent. The sum rules apply to the payload.

Absence in a match expression.
An annotation is meant to give its type to every arm of a match expression. rux 0.4.0 does not yet do that when one arm is none: let r: int32? = match n { 0 => none, else => n }; is rejected with 'none' needs an expected optional type, but found 'int32'. Write the present arm as .Some(n).

null is not absence

null is the null raw pointer, and it is not a value of any optional type:

error: 'null' is not a value of type 'int32?'
  help: write 'none' for an absent optional

A raw pointer may be the payload of an optional, (*T)?, when a pointer that might not be there should be checked like any other optional. See Optional pointer.

Matching

An optional is opened with an ordinary match. Its patterns select one level at a time:

PatternMatches
v?a present value, binding its payload to v — shorthand for .Some(v)
.Some(p)a present value whose payload matches the pattern p
noneabsence of the level being matched
0?a present value equal to 0 — any pattern can carry the ? suffix
v??two present levels, binding the inner payload — .Some(.Some(v))
none?a present absence — .Some(none)
v: Ta present value whose payload has type T, binding it (one level)
func Find(values: int32[..], wanted: int32) -> uint? {
    for i in 0..values.length {
        if values[i] == wanted {
            return i;
        }
    }
    return none;
}

func Describe(found: uint?) -> char8[..] {
    return match found {
        0? => "at the front",
        index? => "further in",
        none => "missing"
    };
}

Every level must be covered. A nested optional needs an arm for each of its states:

func Lookup(key: int32) -> int32?? {
    if key == 0 {
        return none;
    }
    if key == 1 {
        return .Some(none);
    }
    return .Some(.Some(key * 10));
}

func Classify(found: int32??) -> char8[..] {
    return match found {
        value?? => "a value",
        none? => "a present absence",
        none => "absent"
    };
}

A typed presence pattern takes exactly one level and leaves the rest to the arm:

func Typed(found: int32??) -> int32 {
    return match found {
        stored: int32? => stored ?? -1,
        none => -2
    };
}

A match that leaves a state out names it:

error: match on 'int32?' is not exhaustive; missing none
error: match on 'int32?' is not exhaustive; missing .Some(_)
error: match on 'int32??' is not exhaustive; missing .Some(none)

Using the value

An optional is not its payload. It has no arithmetic, no fields, and no Display, so known * 2 with known: int32? is error: operator '*' cannot combine left operand 'int32?' with right operand 'int'. The value inside is reached in one of four ways:

ToolUse it when
matcheach state needs its own code
??absence has a stand-in value, or should leave the loop or function
?absence should make the whole function's result absent
isonly presence matters: reading is int32 is true when present

Equality

== and != compare optionals level by level: absent equals absent, and two present values are equal when their payloads are. Both operands must have the same optional type. none, .Some(…) and an unsuffixed literal take the other operand's type, but a value with a type of its own is never wrapped for a comparison:

let known: int32? = 5;
let a = known == 5;          // true: the literal is made present
let b = known == .Some(5);   // true
let c = known == none;       // false

known == 5i32 is rejected:

error: operator '==' cannot compare 'int32?' with 'int32'
  note: both operands of a native comparison have the same type; a comparison never injects, widens, or wraps an operand
  help: write '.Some(...)' or another constructor, bind the member with a typed pattern, or match the value

Iteration

The iterator convention is built on optionals: an iterator's Next returns Item?, and a for loop ends at the first outer absence. Because levels never collapse, an iterator whose items are themselves optional returns Item??, and an absent item does not end the loop. See Iteration.

No methods

An optional is a compiler-owned form with no declaring package, so it cannot be extended and implements no interface — an int32? is not Display, even though int32 is:

error: cannot extend native type 'int32?'
  note: a sum, optional, fallible, or unit type has no declaring package to own methods or interface implementations
  help: write a generic function that takes the native type as a parameter

A reusable operation on optionals is a generic free function, such as func OrZero<T>(value: T?, zero: T) -> T. Inference reaches through T? as it does through any generic type.

Layout

An optional is stored as an 8-byte tag followed by its payload, aligned for the payload; a zero-sized payload takes no room. sizeof(int32?) and sizeof(int64?) are 16, sizeof(int32??) is 24 and sizeof(()?) is 8. The tag values are not a stable ABI. See Layout.

See also