โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
#[derive(Debug)]
enum AppErr { Parse(ParseIntError), Range(i32), DivZero }
fn parse(s: &str) -> Result<i32, AppErr> {
s.parse().map_err(AppErr::Parse) // convert ParseIntError to AppErr
}
fn validate(n: i32) -> Result<i32, AppErr> {
if n >= 1 && n <= 100 { Ok(n) } else { Err(AppErr::Range(n)) }
}
// ? chains โ each step propagates errors automatically
fn process(s: &str) -> Result<i32, AppErr> {
let n = parse(s)?; // Err(ParseError) if not a number
let v = validate(n)?; // Err(Range) if out of bounds
Ok(v * v) // success
}
// match on Result โ explicit handling
match process("42") {
Ok(v) => println!("got {}", v),
Err(e) => println!("failed: {:?}", e),
}
// Combinators โ like Option
let r: Result<i32, _> = Ok(5);
r.map(|x| x * 2); // Ok(10)
r.map_err(|e| format!("error: {:?}", e)); // transforms the error
// collect() on Vec<Result<T, E>> โ all-or-nothing
let inputs = vec!["1", "2", "3"];
let nums: Result<Vec<i32>, _> = inputs.iter().map(|s| parse(s)).collect();
// Ok([1, 2, 3]) โ all succeed
let mixed = vec!["1", "x", "3"];
let result: Result<Vec<i32>, _> = mixed.iter().map(|s| parse(s)).collect();
// Err(Parse(...)) โ first failure wins
| Concept | OCaml | Rust |
|---|---|---|
| Type | `('a, 'e) result` | `Result<T, E>` |
| Variants | `Ok x`, `Error e` | `Ok(x)`, `Err(e)` |
| Chain/bind | `Result.bind r f` or `let` | `r.and_then(f)` or `?` |
| Early return on error | Via `let` monad | `?` operator |
| Map error | `Result.map_error f r` | `r.map_err(f)` |
| Collect | Manual fold | `collect::<Result<Vec<_>, _>>()` |
use std::num::ParseIntError;
#[derive(Debug)]
enum Err { Parse(ParseIntError), Range(i32), DivZero }
impl std::fmt::Display for Err {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Err::Parse(e) => write!(f, "parse: {}", e),
Err::Range(n) => write!(f, "{} out of range", n),
Err::DivZero => write!(f, "div by zero"),
}
}
}
fn parse(s: &str) -> Result<i32, Err> { s.parse().map_err(Err::Parse) }
fn validate(n: i32) -> Result<i32, Err> {
if n>=1 && n<=100 { Ok(n) } else { Err(Err::Range(n)) }
}
// ? operator chains
fn process(s: &str) -> Result<i32, Err> {
let n = parse(s)?;
let v = validate(n)?;
Ok(v * v)
}
fn main() {
for s in ["42","abc","0","100","101"] {
match process(s) {
Ok(v) => println!("{} -> {}", s, v),
Err(e) => println!("{} -> Err: {}", s, e),
}
}
// combinators
let r: Result<i32,Err> = Ok(5);
println!("map: {:?}", r.map(|x|x*2).ok());
// collect: Vec<Result> -> Result<Vec>
let ok_strs = vec!["1","2","3"];
let ok_nums: Result<Vec<i32>,_> = ok_strs.iter().map(|s|parse(s)).collect();
println!("collect ok: {:?}", ok_nums.ok());
let bad_strs = vec!["1","x","3"];
let bad: Result<Vec<i32>,_> = bad_strs.iter().map(|s|parse(s)).collect();
println!("collect bad err: {}", bad.is_err());
}
#[cfg(test)]
mod tests {
use super::*;
#[test] fn ok() { assert!(process("42").is_ok()); }
#[test] fn abc() { assert!(process("abc").is_err()); }
#[test] fn zero() { assert!(process("0").is_err()); }
}
(* Result chaining in OCaml *)
let (let*) = Result.bind
let parse s = match int_of_string_opt s with
| Some n -> Ok n | None -> Error (Printf.sprintf "not int: %s" s)
let validate n =
if n>=1 && n<=100 then Ok n else Error (Printf.sprintf "%d out of range" n)
let process s =
let* n = parse s in
let* v = validate n in
Ok (v*v)
let () =
List.iter (fun s ->
match process s with
| Ok v -> Printf.printf "%s->%d\n" s v
| Error e -> Printf.printf "%s->Err:%s\n" s e
) ["42";"abc";"0";"100";"101"]