ExamplesBy LevelBy TopicLearning Paths
047 Intermediate

047 — Result Bind (and_then)

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "047 — Result Bind (and_then)" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. `Result::and_then` (bind) sequences two fallible operations: apply a function that returns `Result` to the `Ok` value, propagating `Err` automatically. Key difference from OCaml: 1. **`?` vs `let*`**: Rust's `?` is built into the language. OCaml's `let*` requires the `ppx_let` preprocessor. The `?` operator is more ergonomic for long chains.

Tutorial

The Problem

Result::and_then (bind) sequences two fallible operations: apply a function that returns Result to the Ok value, propagating Err automatically. It is the monadic sequencing operation for Result — "do this, then do that, stopping at the first failure". This enables clean pipelines of fallible operations without nested match statements.

The classic example is a multi-step pipeline: validate input → parse → compute → format. Each step may fail; if any fails, the pipeline stops and propagates the error. Without and_then, this requires nested match blocks or repeated null-checking. With and_then, the happy path reads linearly.

🎯 Learning Outcomes

  • • Use result.and_then(|v| fallible_transform(v)) to chain fallible operations
  • • Understand the difference from map: and_then applies a function returning Result
  • • Build multi-step pipelines where each step can independently fail
  • • Recognize and_then as the result monad's bind operation
  • • Connect and_then to the ? operator: f()? desugars to an and_then chain
  • • Use Result::and_then(f) to sequence fallible operations — each step feeds its success value to the next
  • • Use the ? operator as shorthand for and_then-based error propagation in functions returning Result
  • Code Example

    #![allow(clippy::all)]
    // Placeholder — pending conversion

    Key Differences

  • **? vs let***: Rust's ? is built into the language. OCaml's let* requires the ppx_let preprocessor. The ? operator is more ergonomic for long chains.
  • Error type unification: Rust's ? operator can automatically convert error types via the From trait — ? on Result<T, IoError> in a function returning Result<T, AppError> calls AppError::from(e). OCaml requires explicit Result.map_error.
  • **and_then vs >>=**: Haskell uses >>= (pronounced "bind"). Rust calls it and_then. OCaml calls it bind. All are the same operation.
  • Error channel: and_then only threads through the success channel. Use or_else for the error channel: result.or_else(|e| fallback(e)).
  • **and_then sequences fallible steps:** Each step in the chain may fail. If any step returns Err, the chain short-circuits and propagates the error. Only Ok values proceed to the next step.
  • Railway-oriented programming: The metaphor: Ok is the happy track; Err is the error track. and_then switches you to the error track on failure — you never switch back without explicit or_else.
  • **? operator shorthand:** let x = fallible_op()? is syntactic sugar for and_then in functions returning Result. The ? desugars to match fallible_op() { Ok(v) => v, Err(e) => return Err(e.into()) }.
  • **OCaml let*:** With ppx_let, let* x = fallible_op () in next_step x is the OCaml equivalent of fallible_op()?.and_then(|x| next_step(x)).
  • OCaml Approach

    OCaml's Result.bind r f: let bind r f = match r with Ok x -> f x | Error e -> Error e. Pipe style: parse_int s |> Result.bind safe_div |> Result.bind (fun n -> Ok (string_of_int n)). With let* (ppx_let): let* n = parse_int s in let* d = safe_div n 2 in Ok (string_of_int d) — this reads like imperative code with automatic error propagation.

    Full Source

    #![allow(clippy::all)]
    // Placeholder — pending conversion

    Deep Comparison

    OCaml vs Rust: Result Bind

    Overview

    See the example.rs and example.ml files for detailed implementations.

    Key Differences

    AspectOCamlRust
    Type systemHindley-MilnerOwnership + traits
    MemoryGCZero-cost abstractions
    MutabilityExplicit refmut keyword
    Error handlingOption/ResultResult<T, E>

    See README.md for detailed comparison.

    Exercises

  • Config parser: Write a function that reads a string "host:port", splits on :, parses the port as u16, and validates the host is non-empty. Use and_then at each step.
  • Database query chain: Simulate find_user(id).and_then(|u| find_posts(u.id)).and_then(|posts| render(posts)) with stub functions. Handle the case where each step might return Err("not found").
  • Equivalent forms: Rewrite a 3-step and_then chain using (a) nested match, (b) and_then, (c) ? operator. Verify all three produce the same results. Discuss readability.
  • Pipeline of 3: Write pipeline(input: &str) -> Result<f64, String> that parses a string to integer, checks it's positive, then computes its square root — using three chained and_then calls.
  • **? operator**: Rewrite the same pipeline function using the ? operator instead of explicit and_then chains. Both must produce identical behavior.
  • Open Source Repos