β’ 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
fn parse_int(s: &str) -> Result<i64, AppError> { ... }
fn check_range(x: i64, min: i64, max: i64) -> Result<i64, AppError> { ... }
// map: transform Ok value (plain function, can't fail)
let doubled = parse_int("5").map(|x| x * 2);
// Ok(10) β the closure never produces an Err
// map_err: transform the error type (e.g. to log-friendly String)
let stringified = parse_int("bad").map_err(|e| format!("{:?}", e));
// Err("ParseError(...)")
// and_then: chain a fallible operation
// Use this when the next step can ALSO return Err
let result = parse_int("100")
.map(|x| x / 4) // infallible: just math
.and_then(|x| check_range(x, 0, 50)); // fallible: might be out of range
// Ok(25) β 100/4=25, 25 is in [0,50]
// Errors short-circuit: the chain stops at the first Err
let result2 = parse_int("1000")
.map(|x| x / 4) // Ok(250)
.and_then(|x| check_range(x, 0, 50)); // Err(OutOfRange(250))
// Err β check_range fails, map step was wasted but harmless
// Collect a Vec of Results into Result<Vec<...>>
// None if ANY element is Err β the whole thing fails
let parsed: Result<Vec<i64>, AppError> =
vec!["1", "2", "3"].iter().map(|s| parse_int(s)).collect();
// Ok([1, 2, 3])
| Concept | OCaml | Rust | ||
|---|---|---|---|---|
| Transform Ok value | `Result.map f r` | `r.map(f)` | ||
| Transform Err value | `Result.map_error f r` | `r.map_err(f)` | ||
| Chain fallible op | `Result.bind r f` or `r >>= f` | `r.and_then(f)` | ||
| Recover from error | `Result.catch` / custom | `r.or_else(\ | e\ | ...)` |
| All-or-nothing collect | `List.map f xs \ | > Result.all` | `xs.iter().map(f).collect::<Result<Vec<_>,_>>()` | |
| Chaining style | `>>=` operator (infix) | Method chain (`.and_then(...)`) |
// Result Map β 99 Problems #46
// Transform Result values with map, map_err, and_then, or_else.
#[derive(Debug, PartialEq, Clone)]
enum AppError {
ParseError(String),
DivisionByZero,
OutOfRange(i64),
}
fn parse_int(s: &str) -> Result<i64, AppError> {
s.trim().parse::<i64>()
.map_err(|_| AppError::ParseError(format!("'{}' is not an integer", s)))
}
fn safe_div(a: i64, b: i64) -> Result<i64, AppError> {
if b == 0 { Err(AppError::DivisionByZero) } else { Ok(a / b) }
}
fn check_range(x: i64, min: i64, max: i64) -> Result<i64, AppError> {
if x >= min && x <= max { Ok(x) } else { Err(AppError::OutOfRange(x)) }
}
/// Double a parsed integer.
fn parse_and_double(s: &str) -> Result<i64, AppError> {
parse_int(s).map(|x| x * 2)
}
/// Transform error type.
fn parse_as_string_error(s: &str) -> Result<i64, String> {
parse_int(s).map_err(|e| format!("{:?}", e))
}
fn main() {
// map transforms Ok, passes Err through
println!("parse('5').map(*2) = {:?}", parse_and_double("5"));
println!("parse('x').map(*2) = {:?}", parse_and_double("x"));
// map_err transforms Err, passes Ok through
println!("map_err: = {:?}", parse_as_string_error("bad"));
// Chaining maps
let r = parse_int("100")
.map(|x| x / 4)
.and_then(|x| check_range(x, 0, 50));
println!("100 / 4 in [0,50] = {:?}", r);
let r2 = parse_int("1000")
.map(|x| x / 4)
.and_then(|x| check_range(x, 0, 50));
println!("1000 / 4 in [0,50] = {:?}", r2);
// or_else: recover from errors
let recovered = safe_div(10, 0).or_else(|_| Ok::<i64, AppError>(-1));
println!("div 0 recovered: = {:?}", recovered);
// collect Results from iterator
let strings = vec!["1", "2", "3"];
let parsed: Result<Vec<i64>, AppError> = strings.iter().map(|s| parse_int(s)).collect();
println!("collect all ok: = {:?}", parsed);
let mixed = vec!["1", "x", "3"];
let mixed_result: Result<Vec<i64>, AppError> = mixed.iter().map(|s| parse_int(s)).collect();
println!("collect with error: = {:?}", mixed_result);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_map_ok() {
assert_eq!(parse_and_double("5"), Ok(10));
}
#[test]
fn test_map_err_pass_through() {
assert!(parse_and_double("x").is_err());
}
#[test]
fn test_map_err_transform() {
assert!(parse_as_string_error("bad").is_err());
let e = parse_as_string_error("bad").unwrap_err();
assert!(e.contains("ParseError"));
}
#[test]
fn test_collect_ok() {
let strings = vec!["1", "2", "3"];
let result: Result<Vec<i64>, AppError> = strings.iter().map(|s| parse_int(s)).collect();
assert_eq!(result, Ok(vec![1, 2, 3]));
}
#[test]
fn test_or_else_recovery() {
let r = safe_div(10, 0).or_else(|_| Ok::<i64, AppError>(0));
assert_eq!(r, Ok(0));
}
}
(* Result Map *)
(* OCaml 99 Problems #46 *)
(* Implementation for example 46 *)
(* Tests *)
let () =
(* Add tests *)
print_endline "β OCaml tests passed"