Skip to content

Optional Types

Accepted

Accepted for V1 optional typing, presence coercion, defaulting, forced unwrap, binding ownership, and focused type reflection.

?T is an optional type: either a T value or null.

var maybe_value: ?i32 = null

Optional types are ordinary concrete Type values when T is concrete. They are used for absence that is not an error, such as iterator completion:

fn next(self: *Self) ?Item

null is the absence value for optional types.

Boolean Coercion

Optionals coerce to bool: null is false and a present value is true. The coercion tests only the outer optional layer. For ??T, null is false, while a present inner optional is true even if the inner value is null.

This rule is uniform for all payload types, including ?void and ?bool. For ?bool, the coercion tests presence, not the boolean payload: Some(false) coerces to true. Coercing ?bool to bool should produce a lint because readers may confuse presence with payload truth.

Defaulting and Forced Unwrap

a orelse b unwraps one optional layer or evaluates a fallback. The left operand must have type ?T; the right operand is expected to have type T; the expression has type T.

const label = maybe_label orelse "unknown"

The left operand is evaluated once. If it is present, the payload value is the result and the right operand is not evaluated. If it is null, the right operand is evaluated and becomes the result.

Conceptually, a orelse b is the expression form of:

if const value = a {
  value
} else {
  b
}

For a bare local optional binding, this is equivalent to the same-name optional-binding sugar:

if a {
  a
} else {
  b
}

a.? is forced optional unwrap. The operand must have type ?T, and the expression has type T.

const value = maybe_value.?

If the optional is present, .? returns the payload. If the optional is null, the operation is a checked safety failure in Checked safety mode and traps in a deterministic panic-like way outside ordinary error returns. Diagnostics and trap reports should identify the forced unwrap source span. In Unchecked safety mode, the check may be omitted; programs must not rely on the behavior of unwrapping null.

Prefer explicit control flow when absence is expected

.? is for invariants the program expects to hold. Use optional binding or orelse when absence is an ordinary outcome.

Binding and Ownership

Conditional declaration forms such as if const value = maybe and while const item = iter.next() unwrap one present optional payload into a local binding.

Optional binding is value binding, not hidden borrowing. Lvalue optional binding copies the payload and therefore requires a copyable payload type. Fresh rvalue optionals may move the payload.

No lvalue move-out

V1 does not support destructive move-out-and-set-null from lvalue optionals, even with move maybe. Broader pattern syntax is deferred.

See Optional Bindings for conditional binding, same-name if sugar, field optional behavior, and resource payload ownership.

Optional Reflection

Optional reflection uses focused Type APIs rather than a separate public Type.Optional metadata object:

T.is_optional() Type.Predicate
T.optional_payload() Type!Type.ReflectionError

is_optional() is true for optional type forms such as ?i32 and carries a primitive type-kind fact when used as a Type.Predicate.

optional_payload() returns the payload type for one optional layer. For ??i32, it returns ?i32. Calling it on a non-optional type reports Type.ReflectionError.