Optional
The Optional type provides a safe way to represent values that may or may not
be present, instead of relying on sentinel values such as null.
- The nominal type constructor is
Option(T). - The shorthand
T?is sugar forOption(T)and is the recommended form. - Optional values are constructed using
Some(...)andNone(the compiler also acceptsnoneas an alias ofNone). - The
nullliteral is distinct fromNone, but may coerce toNonewhen an optional type is expected. - Use
match,?.(optional chaining), and??(coalescing) to consume optionals.
Declaring Optional Types#
You can declare variables or fields as optional using either:
T?(idiomatic suffix form),Option(T)(nominal form).
The language design treats these as equivalent.
Implementation
-
The type system (
src/types.zig) models optional types, and the parser now accepts both: -
the suffix form
T?in type annotations, and -
the nominal form
Option(T)for simple cases (a single type argument), which is desugared into the same internal optional representation asT?. -
For example, the following is valid today and type-checks successfully (note that the current compiler requires
letinitializers; see diagnostics,E2015):fn main () -> int { let a: string? = None; let b: Option(string) = None; return 0; } -
The current
linux/x86_64IR→ELF backend subset now supports a first slice of optional values for a subset of payload types: -
construct optionals via
NoneandSome(value), -
access fields of optional structs via optional chaining (
opt?.field, producing aFieldType?value), -
call methods on optional structs via optional chaining (
opt?.method(args), producing aResultType?value), -
use nested optionals (
T??) for a subset of payloads in the current backend (see below), -
compare supported optionals via
==/!=(tag + payload equality; nested optionals compare recursively), -
unwrap optionals via
??(coalescing) with short-circuit evaluation of the fallback expression, -
explicitly branch on optionals via the
matchexpression (see flow match), -
and pass/return such optionals between helpers in the supported IR subset.
Supported optional payloads in this backend subset include:
- scalars (
bool,char,f32,f64,int, and fixed-width integers), string(lowered as{ ptr: u64, len: i64 }),- enums (tagged unions) in the current enum backend subset (lowered as
(u64 tag, payload_0, payload_1, ...)), - and the supported
structsubset (0+ fields of supported value types, including nested structs and optionals; see structs impls layout).
In this subset, optionals are represented at IR boundaries as a Bool tag
followed by the payload scalars: (Bool tag, payload0, payload1, ...) where
tag=0 means None and tag=1 means Some(...). The payload scalar slots
follow the same lowering rules as the underlying non-optional type (1 scalar
for scalar payloads, 2 scalars for string, N scalars for the current struct
subset, and N scalars for enums (including the enum’s own u64 tag slot).
Nested optionals (T??) are supported in this backend subset for the same
payload subset (scalars, string, enums, and the supported struct subset).
In this subset, T?? is represented as an outer optional whose payload is
the full inner optional representation: for example int?? lowers as
(Bool tag0, Bool tag1, i64 payload).
- Not yet implemented:
- optional chaining beyond the current optional-struct field access and optional method call subsets (for example chaining through optional fields and optional indexing),
matchover non-optional scrutinee types (and richer pattern forms beyondNone/Some(...)),- and richer optional forms beyond the backend.
Note: optional payload equality (== / !=) is still limited in the current
backend subset; comparisons against None are supported broadly, but full
payload equality for all optional payload kinds (notably optional-of-enum) is
still evolving.
For the current C ABI mapping of optionals in exported function signatures within the supported backend subset, see abi libsilk and ext.
Creating Optional Values#
An optional can be:
None— the empty state.Some(value)— the value‑holding state.
Examples from the spec:
let age: u32? = None;let age: u32? = Some(30);struct User = { profile: None };profile: Some({ email: "some@example.com", age: Some(30) })
The compiler infers the optional’s element type from context when possible.
Equality comparisons provide optional type
context for None / Some(...) operands, so forms like opt == None and
opt == Some(value) type-check when opt has type T?.
None: The Empty State#
None represents the absence of a value.
Spelling note: None may also be written as none (alias). The null literal
is a distinct literal that can coerce to None in optional contexts.
Key points:
Nonecan be assigned to anyT?; its concreteTis inferred.- In pattern matching and control flow,
Nonecorresponds to the empty branch.
Some(value): The Value-Holding State#
Some(value) wraps a concrete value in an Option(T).
Key points:
- The type of
Some(value)isT?(orOption(T)). - Nested optionals are allowed (e.g. a struct containing fields that are
T?).
Optional-Coalescing Operator ??#
The ?? operator unwraps an optional by providing a fallback value if it is None.
From the spec:
- It “coalesces” the optional’s value and the default into a single, non‑optional result.
- The expression
opt ?? default_valuehas typeTwhenopthas typeT?. - When
Tis a struct, the left operand supplies the contextual type for an anonymous aggregate fallback. For example,failed_opt ?? { code: 0 }is equivalent tofailed_opt ?? Failed{ code: 0 }whenfailed_opthas typeFailed?; an explicit type annotation on the surroundingletis not required. - When
opthas typeT??, the expressionopt ?? default_valuehas typeT?(it unwraps one optional layer). - It composes naturally with optional chaining.
Example:
let email_address: string = user2.profile?.email ?? "no-email-provided@domain.com";
Scope note:
??is primarily the optional-coalescing operator.- The same token is also used for recoverable
Result-like values:result ?? fallbackyields theOk(...)payload or the fallback forErr(...). - The same token is also used for ordinary named enums with exactly two declared variants:
- if the first declared variant is unit,
value ?? fallbackyields that enum value, - if the first declared variant carries exactly one payload, it yields that payload,
- and if the value is the second declared variant,
fallbackis evaluated. - The right-hand side may also be one of the narrow terminal control-flow
forms accepted only after
??: value ?? return exprvalue ?? breakvalue ?? continue- These forms keep the same validity rules as their statement counterparts:
returnmust be valid in the enclosing function and type-check against its result type,breakandcontinueare only valid inside loops.- This is still a narrow rule for coalescing. It does not make
return,break, orcontinuegeneral expressions elsewhere in the language. - The optional and recoverable-result forms are distinguished by the left-hand
operand type; expression
matchremains the more general payload-aware tool when you need explicit names, multiple payload elements, or more than two states.
Examples:
fn read_port () -> int {
let port: int = maybe_port() ?? return 80;
return port;
}
fn drain () -> int {
let mut seen: int = 0;
loop {
let value: int = next_value() ?? break;
seen = value;
}
return seen;
}
fn scan (values: int?[]) -> int {
let mut found: int = 0;
for item in values {
let value: int = item ?? continue;
found = value;
}
return found;
}
Using Optional Values#
The spec provides several mechanisms for working with optionals:
- Optional chaining
?.: user.profile?.emailyieldsstring?.- If any link in the chain is
None, the result isNone. - Optional method calls are also supported:
user.profile?.email_len()yieldsint?.- When the receiver is
Some(v), the call evaluates asSome(v.email_len()). - When the receiver is
None, the call evaluates asNone. - Coalescing
??: - Converts an optional into a non‑optional by supplying a default.
- Explicit checking via
match: - Pattern‑matching on
Some(...)/Noneto handle both cases explicitly.
Optional combinators (methods)#
In addition to match, ?., and ??, the compiler provides a small set of
combinator methods on optional values (T?). These are designed to feel
familiar to Rust developers while preserving Silk’s explicit move/cleanup rules
(memory model).
Supported methods (Supported forms):
opt.is_some() -> boolopt.is_none() -> boolopt.map(f) -> U?wheref: fn(T) -> Uopt.and_then(f) -> U?wheref: fn(T) -> U?opt.or_else(f) -> T?wheref: fn() -> T?opt.unwrap_or(fallback) -> T(eager;fallbackis evaluated before the call)opt.unwrap_or_else(f) -> Twheref: fn() -> T(lazy; called only forNone)
Notes:
??remains the idiomatic lazy fallback operator because the fallback is an ordinary expression and is evaluated only forNone.unwrap_oris eager by design; useunwrap_or_else(or??) when the fallback is expensive.map/and_thencall the callback only forSome(...).
Example (map + and_then):
import std::result;
fn parse_port (s: string) -> std::result::Result(int, int) {
return Ok(123);
}
fn main () -> int {
let maybe: string? = Some("8080");
let port_opt: int? = maybe.and_then(fn (s: string) {
return parse_port(s).ok_value();
});
return port_opt.unwrap_or(0);
}
Compiler Requirements#
The compiler must:
- Support
T?andOption(T)as equivalent surface forms. - Ensure that
Some/Noneusage is type‑correct. - Track optionality in the type system and enforce checks when unwrapping.
- Implement
?.and??with the short‑circuit semantics described above. - Support
matchonOption(T)and integrate optionals with flow control and error reporting.
Source repository · Edit this page · View Markdown