โข 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
pub fn safe_div(x: i32, y: i32) -> Option<i32> {
if y == 0 { None } else { Some(x / y) }
}
pub fn safe_head(list: &[i32]) -> Option<i32> {
list.first().copied()
}
// Style 1: and_then (bind) + map โ mirrors OCaml's >>= and >>|
pub fn compute(lst: &[i32]) -> Option<i32> {
safe_head(lst)
.and_then(|x| safe_div(100, x)) // None if list empty or x == 0
.map(|r| r * 2) // None propagates unchanged
}
// Style 2: ? operator โ monadic chaining that reads like imperative code
pub fn compute_q(lst: &[i32]) -> Option<i32> {
let x = safe_head(lst)?; // returns None from function if absent
let r = safe_div(100, x)?; // returns None if division fails
Some(r * 2)
}
// Style 3: explicit bind โ shows what and_then desugars to
fn bind<T, U>(opt: Option<T>, f: impl FnOnce(T) -> Option<U>) -> Option<U> {
match opt {
None => None,
Some(x) => f(x), // only calls f if we have a value
}
}
All three styles produce identical results. `and_then` is composable; `?` is readable; explicit `bind` is educational.
| Concept | OCaml | Rust | |
|---|---|---|---|
| Bind operator | Custom `>>=` infix | `.and_then()` method | |
| Map operator | Custom `>> | ` infix | `.map()` method |
| Sugar | `let*` in `Option` monad context | `?` operator | |
| `?` equivalent | None โ no direct equivalent | `?` desugars to early `return None` | |
| Stdlib bind | `Option.bind` (OCaml 4.08+) | `and_then` โ available from the start |
pub fn safe_div(x: i32, y: i32) -> Option<i32> {
if y == 0 { None } else { Some(x / y) }
}
pub fn safe_head(list: &[i32]) -> Option<i32> {
list.first().copied()
}
// Idiomatic: Option::and_then (bind) + Option::map (fmap)
pub fn compute_idiomatic(lst: &[i32]) -> Option<i32> {
safe_head(lst)
.and_then(|x| safe_div(100, x))
.map(|r| r * 2)
}
// Explicit bind โ shows what and_then desugars to
fn bind<T, U>(opt: Option<T>, f: impl FnOnce(T) -> Option<U>) -> Option<U> {
match opt {
None => None,
Some(x) => f(x),
}
}
pub fn compute_explicit(lst: &[i32]) -> Option<i32> {
let divided = bind(safe_head(lst), |x| safe_div(100, x));
divided.map(|r| r * 2)
}
// Question-mark operator โ ergonomic monadic short-circuit
pub fn compute_question_mark(lst: &[i32]) -> Option<i32> {
let x = safe_head(lst)?;
let r = safe_div(100, x)?;
Some(r * 2)
}
fn main() {
let show = |opt: Option<i32>| match opt {
None => "None".to_string(),
Some(x) => x.to_string(),
};
println!("--- Idiomatic (and_then + map) ---");
println!("compute([5,3,1]) = {}", show(compute_idiomatic(&[5, 3, 1])));
println!("compute([0,1]) = {}", show(compute_idiomatic(&[0, 1])));
println!("compute([]) = {}", show(compute_idiomatic(&[])));
println!("\n--- Explicit bind ---");
println!("compute([5,3,1]) = {}", show(compute_explicit(&[5, 3, 1])));
println!("compute([0,1]) = {}", show(compute_explicit(&[0, 1])));
println!("compute([]) = {}", show(compute_explicit(&[])));
println!("\n--- Question-mark operator ---");
println!("compute([5,3,1]) = {}", show(compute_question_mark(&[5, 3, 1])));
println!("compute([0,1]) = {}", show(compute_question_mark(&[0, 1])));
println!("compute([]) = {}", show(compute_question_mark(&[])));
}
/* Output:
--- Idiomatic (and_then + map) ---
compute([5,3,1]) = 40
compute([0,1]) = None
compute([]) = None
--- Explicit bind ---
compute([5,3,1]) = 40
compute([0,1]) = None
compute([]) = None
--- Question-mark operator ---
compute([5,3,1]) = 40
compute([0,1]) = None
compute([]) = None
*/
(* OCaml option monad: bind and fmap operators *)
(* Monadic bind: >>= propagates None short-circuit *)
let ( >>= ) opt f = match opt with
| None -> None
| Some x -> f x
(* Functor map: >>| transforms the value if present *)
let ( >>| ) opt f = match opt with
| None -> None
| Some x -> Some (f x)
let safe_div x y = if y = 0 then None else Some (x / y)
let safe_head = function [] -> None | h :: _ -> Some h
(* Idiomatic OCaml: operator chaining *)
let compute lst =
safe_head lst >>= fun x ->
safe_div 100 x >>| fun r ->
r * 2
(* Explicit using Option module โ mirrors Rust's and_then + map *)
let compute_explicit lst =
Option.bind (safe_head lst) (fun x ->
Option.map (fun r -> r * 2) (safe_div 100 x))
let () =
let show = function None -> "None" | Some x -> string_of_int x in
assert (compute [5; 3; 1] = Some 40);
assert (compute [0; 1] = None);
assert (compute [] = None);
assert (compute_explicit [5; 3; 1] = Some 40);
Printf.printf "%s\n" (show (compute [5; 3; 1]));
Printf.printf "%s\n" (show (compute [0; 1]));
Printf.printf "%s\n" (show (compute []));
print_endline "ok"
let ( >>= ) opt f = match opt with
| None -> None
| Some x -> f x
let ( >>| ) opt f = match opt with
| None -> None
| Some x -> Some (f x)
let safe_div x y = if y = 0 then None else Some (x / y)
let safe_head = function [] -> None | h :: _ -> Some h
let compute lst =
safe_head lst >>= fun x ->
safe_div 100 x >>| fun r ->
r * 2pub fn safe_div(x: i32, y: i32) -> Option<i32> {
if y == 0 { None } else { Some(x / y) }
}
pub fn safe_head(list: &[i32]) -> Option<i32> {
list.first().copied()
}
pub fn compute_idiomatic(lst: &[i32]) -> Option<i32> {
safe_head(lst)
.and_then(|x| safe_div(100, x))
.map(|r| r * 2)
}fn bind<T, U>(opt: Option<T>, f: impl FnOnce(T) -> Option<U>) -> Option<U> {
match opt {
None => None,
Some(x) => f(x),
}
}
pub fn compute_explicit(lst: &[i32]) -> Option<i32> {
let divided = bind(safe_head(lst), |x| safe_div(100, x));
divided.map(|r| r * 2)
}pub fn compute_question_mark(lst: &[i32]) -> Option<i32> {
let x = safe_head(lst)?;
let r = safe_div(100, x)?;
Some(r * 2)
}| Concept | OCaml | Rust | |
|---|---|---|---|
| Bind operator | `val (>>=) : 'a option -> ('a -> 'b option) -> 'b option` | `fn and_then<U>(self, f: impl FnOnce(T) -> Option<U>) -> Option<U>` | |
| Map operator | `val (>> | ) : 'a option -> ('a -> 'b) -> 'b option` | `fn map<U>(self, f: impl FnOnce(T) -> U) -> Option<U>` |
| Safe division | `val safe_div : int -> int -> int option` | `fn safe_div(x: i32, y: i32) -> Option<i32>` | |
| Safe head | `val safe_head : 'a list -> 'a option` | `fn safe_head(list: &[i32]) -> Option<i32>` |
1. `>>=` is `and_then`: OCaml's custom bind operator and Rust's `Option::and_then` are identical in semantics โ both propagate `None` and apply `f` to the inner value when `Some`. Rust provides this in the standard library; OCaml developers historically defined it themselves.
2. `>>|` is `Option::map`: The functor-map operator in OCaml is exactly `Option::map` in Rust. Clippy will even reject a manual Rust implementation of `fmap` with `match`, telling you to use `.map()` instead โ confirming this identity.
3. The `?` operator desugars to bind: Rust's `?` on an `Option` is syntactic sugar for "return `None` early if `None`, otherwise unwrap". This is monadic short-circuit with imperative-style syntax, unique to Rust and without a direct OCaml equivalent.
4. Value ownership vs. reference semantics: Rust's `and_then` and `map` consume the `Option` by value, and the closures receive owned `T`. OCaml also passes values, but without explicit ownership tracking. In Rust, using `.copied()` on `Option<&T>` to produce `Option<T>` is the idiomatic way to decouple borrowing from the chain.
5. No user-defined infix operators in stable Rust: OCaml makes it natural to define `>>=` as an infix operator. Rust does not support custom infix operators in stable code, so the chaining reads as method calls. This is a deliberate Rust design decision for readability and tooling.
Use `and_then` + `map` when: building a pipeline with multiple fallible steps that reads cleanly left-to-right โ the method chain style makes the data flow obvious and composes well with iterator chains.
Use `?` when: writing code that resembles sequential imperative steps, or when intermediate values need to be named and reused. The `?` style is easier for developers unfamiliar with monadic thinking to read and debug.
Use explicit `bind` (match) when: teaching or documenting what monadic chaining means under the hood, or when porting OCaml code directly for comparison purposes.