Skip to content

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.

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.

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.

? 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))

?. 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.

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())?.