Optional Propagation
Postfix ? on an optional removes one level of it. When the optional is present, value? is its payload and evaluation carries on. When it is absent, the enclosing function returns absence at once, and nothing after the ? runs.
propagate-expr = postfix-expr "?"
The ? is written tight against its operand. With whitespace on both sides, ? is the conditional operator c ? a : b instead; see Conditional.
Semantics
func Parse(text: char8[..]) -> int32? {
if text.length == 0 {
return none;
}
var value = 0i32;
for c in text {
if c < c8'0' || c > c8'9' {
return none;
}
value = value * 10 + ((c - c8'0') as int32);
}
return value;
}
func Sum(left: char8[..], right: char8[..]) -> int32? {
return Parse(left)? + Parse(right)?;
}
Sum("12", "30") is 42. Sum("12", "x") is none: the second ? finds absence and returns it. Each ? operand is evaluated exactly once, from left to right, so in Sum("x", "30") the second Parse never runs.
value? means the same as this match, with the absent arm leaving the function:
match value {
v? => v,
none => return none
}
Leaving through ? is an ordinary return: deferred statements run and live locals are destroyed.
The enclosing function
Absence has to go somewhere, so the function containing the ? must be able to return it. Two return types qualify:
| The function returns | Absence leaves as |
|---|---|
U? | none |
U? ! F | a successful none — a fallible whose success is optional |
U need not be the payload type of the operand; only the absence is passed on. The second form lets one function propagate both absence and failure:
func ReadLine(ok: bool) -> char8[..]? ! IoError {
if !ok {
fail IoError { code: 5 };
}
return "42";
}
func FirstNumber(ok: bool) -> int32? ! IoError {
let line = ReadLine(ok)?; // a failure leaves as a failure
let text = line?; // absence leaves as a successful none
return Parse(text);
}
Absence never becomes an error. In a function returning a plain int32 ! NotFound, ? on an optional is rejected, because there is no absent value to return and ? will not invent an error:
error: '?' propagates the absence of 'int32?', but the enclosing function returns 'int32 ! NotFound'
note: absence leaves as an optional's 'none', or as the successful 'none' of 'U? ! F'; '?' never invents an error for it
help: declare an optional result, as in '-> T?', or supply a fallback with '??'
To turn absence into a failure, say which failure: value ?? fail NotFound {}. See Coalescing. The same message, with 'int' at the end, is what Main gets when it returns a plain int.
One level at a time
? removes exactly one level. On an int32?? the result is an int32?: a present absence is present, so it continues as none instead of leaving.
func Inner(present: bool) -> int32?? {
return present ? .Some(none) : none;
}
func Peel(present: bool) -> int32? {
let once: int32? = Inner(present)?;
return once;
}
Two levels need two propagations, and they must be grouped. ?? is a single token — the coalescing operator — so Inner(present)?? is a ?? missing its right operand (error: expected an expression after '??' before ';'). Write:
func PeelBoth(present: bool) -> int32? {
return (Inner(present)?)?;
}
? and ?? compose: Inner(present)? ?? 17 returns absence for an outer none, and uses 17 for a present absence.
Restrictions
- The operand must be a native optional or a native fallible. Anything else is
error: 'int32' cannot be propagated with '?' because it is neither a native fallible nor an optional. - A borrowed optional is never an operand —
?moves the payload onward, and a reference owns nothing to move:error: '?' cannot consume the borrowed value '&(int32?)' help: match the borrowed value to inspect it, or propagate an owned value ?consumes its operand. A named copyable optional is copied; a named move-only one is written(<-value)?. See Copy and move.
See also
- Optionals — the type and its patterns
- Coalescing —
??when absence has a stand-in - Error propagation — the same
?on a fallible - Learn: Optional propagate, Absence to error