Result Combinators
Combinators let you compose operations on Result without intermediate match
or unwrap. Each method takes a closure and returns a new Result:
| Method | When to use |
|---|---|
.map(f) |
Transforms the Ok value; Err passes through unchanged |
.map_err(f) |
Transforms the error; Ok passes through unchanged |
.and_then(f) |
Chains an operation that also returns Result |
.or_else(f) |
Tries to recover from an Err |
map, map_err, and_then, or_else and a full chained pipeline.
04-result-combinators.zolo
// Feature: Result combinators — map, map_err, and_then, or_else
// Syntax: `r.map(fn)`, `r.map_err(fn)`, `r.and_then(fn)`, `r.or_else(fn)`
// When to use: transform/recover without unwrapping.
use std::Result
fn divide(a: int, b: int) -> Result<int, str> {
if b == 0 {
return Result.Err("division by zero")
}
return Result.Ok(a / b)
}
// -- map: transforms the Ok value, leaves Err passthrough -----
let r1 = divide(10, 2).map(|v| v * 10)
print(r1.unwrap()) // 50
let r2 = divide(10, 0).map(|v| v * 10)
print(r2.is_err()) // true (map didn't touch the error)
// -- map_err: transforms the error, leaves Ok passthrough -----
let r3 = divide(10, 0).map_err(|e| "math: {e}")
print(r3.unwrap_err()) // math: division by zero
let r4 = divide(10, 2).map_err(|e| "ignored: {e}")
print(r4.unwrap()) // 5 (map_err doesn't touch Ok)
// -- and_then: chains an operation that returns Result --------
fn safe_double(n: int) -> Result<int, str> {
if n > 1000 {
return Result.Err("overflow")
}
return Result.Ok(n * 2)
}
let r5 = divide(20, 4).and_then(safe_double)
print(r5.unwrap()) // 10
// Err stops the chain at the first error:
let r6 = divide(20, 0).and_then(safe_double)
print(r6.is_err()) // true (safe_double was never called)
// -- or_else: recovers from Err -------------------------------
let r7 = divide(10, 0).or_else(|_| Result.Ok(0))
print(r7.unwrap()) // 0
let r8 = divide(10, 2).or_else(|_| Result.Ok(999))
print(r8.unwrap()) // 5 (Ok passes through)
// -- Full pipeline --------------------------------------------
fn pipeline(x: int) -> Result<int, str> {
return divide(x, 2).and_then(safe_double).map(|v| v + 1)
}
print(pipeline(10).unwrap()) // (10/2)*2 + 1 = 11
print(pipeline(0).unwrap()) // 0/2=0, *2=0, +1 = 1
.and_thenstops at the first failure — useful for pipelines where each step depends on the success of the previous one..or_elsedoes the opposite: it recovers the flow when there is an error.
Challenge
Add a second .and_then to the pipeline that rejects values greater than 100. What happens with pipeline(500)?