Option
Absence is a value, Some(value) or None, and there is no null: a value wraps itself into Some where an Option is expected, and an Option is only ever unwrapped on purpose.
Option<Value> is a value that may or may not be there: Some(value) when it is, None when it is not. Value? is
sugar for Option<Value>, and there is nothing else that models absence - no null, no nil, no implicit
unwrapping. A value where an Option of its type is expected becomes Some(value) on its own.
Example#
const found: Int? = 3
const missing: Int? = None
print found.isSome()
print missing.isNone()
print found.orElse(0)
print "{found} {missing}"Syntax#
Value? Option<Value>
value a present value, where an Option<Value> is expected
Some(value) a present value, written out
None an absent valueRules#
Option<Value>has exactly two cases,Some(value: Value)andNone.SomeandNoneare bare in an expression and in a pattern, because the prelude imports both.A value where an
Optionof its type is expected becomesSome(value). The result of a body andreturn, a binding with an annotation, an argument, a field and a collection element all wrap a plain value, soSomeis written only where the value is anOptionitself or its type is not decided yet.Noneis always written. See Conversions, rule 8.fn half(value: Int): Int? { if value % 2 != 0 { return None } value / 2 } const scores: List<Int?> = [3, None, 5] print "{half(8)} {half(3)} {scores}"isSome()andisNone()ask which case it is;map,flatMapandfiltertransform without unwrapping.mapruns its closure on the value if there is one and staysNoneotherwise;flatMapismapfor a closure that itself returns anOption;filterturnsSomeintoNonewhen the predicate fails.const found: Int? = 3 print found.map({ _ * 2 }) print found.flatMap({ value => if value > 0 { Some value } else { None } }) print found.filter({ _ > 0 })orElseis the??operator, and its fallback is lazy.found ?? 0andfound.orElse(0)are the same call; the fallback expression only runs when theOptionisNone.okOrturns anOptioninto aResult, with a lazy error for theNonecase.expectunwraps or panics with the message given, for the cases where absence would be a bug rather than an outcome.const found: Int? = 3 print found.okOr("missing") print found.expect("should be there")toList()turns anOptioninto a list of zero or one elements, which is the way into a pipeline:map,filterand the rest ofIterateall follow from there once the value is aList.The generated
ShowwritesSome(value)or the bareNone. A case with fields is written out, a case without one is its name - the same rule every other case follows.Value??is anOptionof anOption. Each?wraps once, soInt??isOption<Option<Int>>:Noneis "nothing was looked up",Some(None)is "it was looked up and there was nothing". In an expression??is the fallback; in a type position it is two?. The innerSomeis always written - anOptionnever wraps into another one, and the payload of a writtenSomenever wraps itself - soSome(Some(3))is the one spelling.fn describe(lookup: Int??): String { match lookup { Some(Some(value)) => "found {value}" Some(None) => "looked up, nothing there" None => "never looked up" } } print describe(Some(None))
What this is not#
An Option is not implicitly its value. A value wraps into an Option where one is expected, but the way back
is always written: ??, ?, match or if const.
const found: Int? = 3
print(found ?? 0)const found: Int? = 3
const plain: Int = found
// error: Expected `Int64`, found `Option<Int64>`A value does not wrap twice. An Int where an Int?? is expected is an error, not Some(Some(value)):
const nested: Int?? = 3
// error: Expected `Option<Option<Int64>>`, found `Int64`None is not null, and it does not compare equal to every type. Option<Int> and Option<String> are
different types, each with its own None; a None never stands for "no value of any type".
Related#
- Void and Never - the other two types with no ordinary constructor.
- Bindings - how
?on a type annotation reads asOption. - Result -
Ok/Fail, the typeOption.okOrconverts into.