04 · Operators
Fallible Chaining
The ?> operator: chain calls that may fail, short-circuiting on error.
On this page
?> pipes a value into a fallible call and short-circuits if the result is an
error — the fallible cousin of |>. It keeps happy-path chains flat instead of
nesting match on every step:
Chaining fallible calls with ?>; the first error stops the chain.
12-fallible-chaining.zolo
// Feature: Fallible chaining operators — ?> (propagate+pipe), !. (unwrap+chain), ?. (null-safe)
// These three operators form a family: each handles Err/None differently when
// chaining method calls on Result or Option values.
fn doubled(n) { return n * 2 }
// `?>` — propagate-or-pipe.
// If the left side is Err/None, propagates out of the enclosing function
// (like `?`). If it is Ok/Some, pipes the unwrapped value into the RHS
// (like `|>`). Combines both operators in one step.
fn print_items() -> Result {
Result.Ok([1, 2, 3]) ?> .each(|n| print(n))
return Result.Ok(0)
}
let _ = print_items()
// expected: 1
// expected: 2
// expected: 3
// `!.` — force-unwrap then chain.
// Unwraps the Result/Option (panics on Err/None, like .unwrap()), then
// accesses the field or calls the method on the inner value.
let s = Result.Ok("hello")!.to_upper() // unwrap, then call .to_upper()
print(doubled(s.len()))
// expected: 10
// `?.` — null-safe chain on an optional value.
// If the receiver is nil, short-circuits to nil instead of panicking.
// Combined with `??` it gives a safe default. `Option<T>` is a tagged
// wrapper, so unwrap/match it first; `?.` operates on the underlying `T?`.
struct Label { field: str }
let maybe: Label? = nil
print(maybe?.field ?? "none")
// expected: none
// `?>` is exactly `lhs? |> rhs`, even when nested inside another call.
// Moving this expression into or out of a `let` never changes which function
// receives the propagated error.
fn source(ok: bool) -> Result<int, str> {
if ok { return Result.Ok(20) }
return Result.Err("boom")
}
fn plus_one(value: int) -> int { value + 1 }
fn nested(ok: bool) -> Result<int, str> {
return Result.Ok((source(ok) ?> plus_one) * 2)
}
match nested(true) {
Result::Ok(value) => print(value),
Result::Err(message) => print(message),
}
match nested(false) {
Result::Ok(value) => print(value),
Result::Err(message) => print(message),
}
// expected: 42
// expected: boom
See also