Propagation with ?
The postfix ? is E#‘s value-flow propagation operator. It unwraps a Result on success and returns its
error from the enclosing fallible function on failure. It is an expression operator, not a statement form.
Grammar
Section titled “Grammar”TryUnwrap = Postfix "?" .Postfix = Primary { Selector } .Selector = "." identifier | "(" [ ArgList ] ")" | "[" Expr "]" | "?" | "?." identifier | "with" "{" FieldInitList "}" .The binder shall require the operand of postfix ? to be Result<T, E>. Any other type is
ES2191. The operator’s expression type is T.
Success and error paths
Section titled “Success and error paths”For value?, evaluate value exactly once. If it is ok(payload), the operator yields payload in
place. If it is error(problem), the enclosing function returns a fresh Result<TOk, E> carrying that
same error value. The success type is retargeted to the enclosing return type; the error type is preserved.
func parsePort(text: string) -> Result<int, string> = parseInt(text)
func describe(text: string) -> Result<string, string> { let port = parsePort(text)? return ok("port {port}")}When parsePort fails, describe returns Result<string, string>.Error without evaluating the final
ok expression. This shared-error-type rule is why a module commonly chooses one error union for its
fallible call graph.
Expression positions
Section titled “Expression positions”? may occur wherever an expression is accepted. These forms have identical propagation semantics:
let port = parsePort(text)?return ok(parsePort(text)? + 1)write(parsePort(text)?)validate(parsePort(text)?)parsePort(text)?The last form discards the successful value but still propagates an error. Each ? resolves one
ok/error fork. Write sequential bindings rather than relying on a compound expression to propagate several
independent results at once:
let host = parseHost(text)?let port = parsePort(text)?return ok(connect(host, port))Boundaries with other question-mark forms
Section titled “Boundaries with other question-mark forms”?. is one lexical token and always denotes null-conditional access. Thus result?.Value is not
try-unwrap followed by a member access; write (result?).Value or bind the unwrapped result first.
The ternary ? : is selected when the following token begins an expression; otherwise ? is postfix.
?? is null coalescing and applies to optional/reference absence, not Result error propagation.
Async functions
Section titled “Async functions”Inside a function whose body awaits, an error path completes the generated async builder with the early
Result rather than emitting a raw method return from the state machine. The source rule is unchanged:
await fetch()? first awaits the result and then propagates its error. Parenthesize the await where the
precedence needs to be explicit: (await fetch())?.
See also
Section titled “See also”- Result values and operations — construction and case inspection.
- Expressions — precedence and
?./??/ ternary syntax. - Exceptions & cleanup — the exceptional boundary and catch-and-translate.