ExamplesBy LevelBy TopicLearning Paths
190 Expert

Effect Handlers

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Effect Handlers" functional Rust example. Difficulty level: Expert. Key concepts covered: Functional Programming. An effect handler is the runtime component that intercepts effects, provides a value, and resumes the computation. Key difference from OCaml: 1. **Handler composition**: OCaml's nested `match_with` calls compose handlers automatically; Rust's simulation requires explicit forwarding of unhandled effects.

Tutorial

The Problem

An effect handler is the runtime component that intercepts effects, provides a value, and resumes the computation. Different handlers for the same effects give different behaviors — the same program can run in a logging context, a testing context, or a production context. This is the effect-system equivalent of the free monad interpreter pattern, but operating at the runtime level rather than the data-structure level.

🎯 Learning Outcomes

  • • Implement effect handlers as closures that intercept and respond to effects
  • • See how swapping handlers changes program behavior without modifying the program
  • • Learn how handlers compose (nested handlers for multiple effects)
  • • Understand the relationship between effect handlers and exception handlers
  • Code Example

    #![allow(clippy::all)]
    // Stub — awaiting conversion from OCaml source.

    Key Differences

  • Handler composition: OCaml's nested match_with calls compose handlers automatically; Rust's simulation requires explicit forwarding of unhandled effects.
  • Effect typing: OCaml's effect type system (planned for OCaml 5.x) will statically type which effects a function performs; Rust's simulation is untyped.
  • Resumption semantics: OCaml's handlers can resume the computation at the point of the effect; Rust's simulation uses callback functions instead.
  • Standard uses: OCaml's async concurrency (eio) is built on effects; Rust's async/await uses a different mechanism (state machine transformation).
  • OCaml Approach

    OCaml 5's match_with is the native handler:

    let with_state initial program =
      let state = ref initial in
      match_with program ()
        { effc = fun (type a) (eff : a eff) ->
            match eff with
            | Get -> Some (fun k -> continue k !state)
            | Put v -> Some (fun k -> state := v; continue k ())
            | _ -> None }
    

    Nested match_with handlers compose naturally — each handles its own effects and forwards others upward.

    Full Source

    #![allow(clippy::all)]
    // Stub — awaiting conversion from OCaml source.

    Deep Comparison

    OCaml vs Rust: Effect Handler

    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

  • Implement a with_transaction handler that wraps all state operations in a simulated transaction, rolling back on error.
  • Write a handler that counts the number of each effect type performed.
  • Compose two handlers: one for logging and one for state, showing that both can intercept their respective effects on the same program.
  • Open Source Repos