โข 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, Error> { ... }
fn safe_div(a: i64, b: i64) -> Result<i64, Error> { ... }
fn check_positive(x: i64) -> Result<i64, Error> { ... }
fn check_small(x: i64) -> Result<i64, Error> { ... }
// Style 1: and_then chain
// Each step only runs if the previous returned Ok
fn pipeline(input: &str, divisor: i64) -> Result<i64, Error> {
parse_int(input)
.and_then(|x| safe_div(x, divisor)) // only runs if parse succeeded
.and_then(check_positive) // only runs if div succeeded
.and_then(check_small) // only runs if positive check passed
}
// Style 2: ? operator โ equivalent, reads like normal imperative code
fn pipeline_question(input: &str, divisor: i64) -> Result<i64, Error> {
let x = parse_int(input)?; // if Err: return that Err immediately
let y = safe_div(x, divisor)?;
let z = check_positive(y)?;
check_small(z) // last step: return its Result directly
}
// Both styles produce IDENTICAL results:
pipeline("100", 5) // Ok(20) โ 100/5=20, positive, small
pipeline("100", 0) // Err(DivByZero) โ stops at step 2
pipeline("-100", 5) // Err(Negative) โ stops at step 3
pipeline("5000", 1) // Err(TooLarge) โ stops at step 4
pipeline("abc", 2) // Err(Parse(...)) โ stops at step 1
| Concept | OCaml | Rust |
|---|---|---|
| Bind operator | `>>=` (infix: `r >>= f`) | `.and_then(f)` (method) |
| ? equivalent | `let x = r in ...` (OCaml 4.08+) | `let x = r?;` |
| Early exit | `match` or `let` | `?` returns from current function |
| Error threading | Same error type throughout | Same error type, or use `From` for conversion |
| Function signature | `'a result -> ('a -> 'b result) -> 'b result` | `Result<T,E>` + closure `T -> Result<U,E>` |
| Method vs function | `Result.bind r f` | `r.and_then(f)` |
// Result Bind โ 99 Problems #47
// Chain fallible computations with and_then (bind / flatMap).
#[derive(Debug, PartialEq)]
enum Error {
Parse(String),
DivByZero,
Negative,
TooLarge,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Parse(s) => write!(f, "parse error: {}", s),
Error::DivByZero => write!(f, "division by zero"),
Error::Negative => write!(f, "negative value"),
Error::TooLarge => write!(f, "value too large"),
}
}
}
fn parse_int(s: &str) -> Result<i64, Error> {
s.trim().parse::<i64>().map_err(|_| Error::Parse(s.to_string()))
}
fn safe_div(a: i64, b: i64) -> Result<i64, Error> {
if b == 0 { Err(Error::DivByZero) } else { Ok(a / b) }
}
fn check_positive(x: i64) -> Result<i64, Error> {
if x > 0 { Ok(x) } else { Err(Error::Negative) }
}
fn check_small(x: i64) -> Result<i64, Error> {
if x < 1000 { Ok(x) } else { Err(Error::TooLarge) }
}
/// Full pipeline using and_then.
fn pipeline(input: &str, divisor: i64) -> Result<i64, Error> {
parse_int(input)
.and_then(|x| safe_div(x, divisor))
.and_then(check_positive)
.and_then(check_small)
}
/// Same using the ? operator.
fn pipeline_question(input: &str, divisor: i64) -> Result<i64, Error> {
let x = parse_int(input)?;
let y = safe_div(x, divisor)?;
let z = check_positive(y)?;
check_small(z)
}
fn main() {
let cases = [
("100", 5),
("100", 0),
("-100", 5),
("5000", 1),
("abc", 2),
];
for (s, d) in &cases {
let r = pipeline(s, *d);
println!("pipeline({:?}, {}) = {:?}", s, d, r);
}
println!("\n? operator version:");
for (s, d) in &cases {
let r = pipeline_question(s, *d);
println!("pipeline_q({:?}, {}) = {:?}", s, d, r);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_success() {
assert_eq!(pipeline("100", 5), Ok(20));
}
#[test]
fn test_div_zero() {
assert_eq!(pipeline("100", 0), Err(Error::DivByZero));
}
#[test]
fn test_negative_result() {
assert_eq!(pipeline("-100", 5), Err(Error::Negative));
}
#[test]
fn test_too_large() {
assert_eq!(pipeline("5000", 1), Err(Error::TooLarge));
}
#[test]
fn test_question_matches_and_then() {
for (s, d) in [("100", 5), ("abc", 1), ("-10", 2)] {
assert_eq!(pipeline(s, d), pipeline_question(s, d));
}
}
}
(* Result Bind *)
(* OCaml 99 Problems #47 *)
(* Implementation for example 47 *)
(* Tests *)
let () =
(* Add tests *)
print_endline "โ OCaml tests passed"