🦀 Functional Rust
🎬 Traits & Generics Shared behaviour, static vs dynamic dispatch, zero-cost polymorphism.
📝 Text version (for readers / accessibility)

• Traits define shared behaviour — like interfaces but with default implementations

• Generics with trait bounds: fn process(item: T) — monomorphized at compile time

• Static dispatch (impl Trait) = zero cost; dynamic dispatch (dyn Trait) = runtime flexibility via vtable

• Blanket implementations apply traits to all types matching a bound

• Associated types and supertraits enable complex type relationships

201: The Nested Update Problem — Why Lenses Exist

Difficulty: ⭐⭐⭐ Level: Intermediate A Lens is a composable getter+setter that makes deep immutable updates painless.

The Problem This Solves

You have a deeply nested struct. You need to change one field three levels down. In Rust, because all values are immutable by default (or because you want to preserve the original), you have to rebuild every layer by hand:
let updated = AppConfig {
 server: ServerConfig {
     db: DbConfig {
         port: 5433,
         ..config.server.db.clone()
     },
     ..config.server.clone()
 },
 ..config.clone()
};
That's 8 lines to change one field. Add a fourth nesting level and it becomes 11. The signal-to-noise ratio is awful — the actual intent (change the port to 5433) is buried under structural noise. You might try writing helper functions like `map_server` and `map_db` — that helps, but now you have a proliferation of one-off helpers that don't compose. You can't combine `map_server` with `map_db` in a generic way; you have to write a new function for every path through the struct. A Lens solves this by giving you a reusable, composable get+set pair. Two Lenses snap together like LEGO: a Lens from `AppConfig → ServerConfig` composed with a Lens from `ServerConfig → DbConfig` gives you a Lens from `AppConfig → DbConfig`. This example exists to solve exactly that pain.

The Intuition

Think of a Lens like a labelled drill bit. You have a drill press (the Lens), and you press it into a struct. It knows exactly where to go and can either read what's there or replace it — without disturbing anything else. The key insight: a Lens is just two functions bundled together:
struct Lens<S, A> {
 get: Box<dyn Fn(&S) -> A>,
 set: Box<dyn Fn(A, &S) -> S>,
}
Here `S` is the "source" (the outer struct you hold) and `A` is the "focus" (the field you care about). The Lens doesn't store the struct — it stores the knowledge of how to reach into it.

How It Works in Rust

Step 1 — Define a Lens for each field:
fn server_lens() -> Lens<AppConfig, ServerConfig> {
 Lens::new(
     |c| c.server.clone(),                           // get: extract server
     |s, c| AppConfig { server: s, ..c.clone() },   // set: rebuild with new server
 )
}

fn db_lens() -> Lens<ServerConfig, DbConfig> {
 Lens::new(
     |s| s.db.clone(),
     |d, s| ServerConfig { db: d, ..s.clone() },
 )
}

fn port_lens() -> Lens<DbConfig, u16> {
 Lens::new(
     |d| d.port,
     |p, d| DbConfig { port: p, ..d.clone() },
 )
}
Step 2 — Compose them to reach any depth:
// This composed lens goes all the way from AppConfig to u16 (the port)
let app_db_port = server_lens()
 .compose(db_lens())
 .compose(port_lens());
Composition works by threading the get and set functions: the composed `get` calls the outer `get`, then the inner `get`. The composed `set` calls the outer `get` to extract the intermediate value, calls the inner `set` to build the updated intermediate, then calls the outer `set` to rebuild the top level. Step 3 — Use it:
// Read
let port = (app_db_port.get)(&config); // 5432

// Write — one line, any depth
let updated = (app_db_port.set)(5433, &config);
The struct update boilerplate is now inside the Lens definitions, written once. All call sites are a single line.

What This Unlocks

Key Differences

ConceptOCamlRust
Struct update syntax`{ x with field = v }` — no clone needed`Struct { field: v, ..x.clone() }` — requires `Clone`
Lens typeRecord of two functions, no allocation concern`Box<dyn Fn>` closures — heap allocated
CompositionNatural function composition, infix `\>>``.compose()` method chain
CostGC handles memory; no explicit cloneEvery `set` clones the struct at each level
ErgonomicsVery conciseMore verbose but fully type-safe
// Example 201: The Nested Update Problem — Why Lenses Exist

// === The Problem: Deeply Nested Struct Updates === //

#[derive(Debug, Clone, PartialEq)]
struct DbConfig {
    host: String,
    port: u16,
    name: String,
}

#[derive(Debug, Clone, PartialEq)]
struct ServerConfig {
    db: DbConfig,
    max_connections: u32,
}

#[derive(Debug, Clone, PartialEq)]
struct AppConfig {
    server: ServerConfig,
    debug: bool,
    version: String,
}

// Approach 1: Manual nested update — clone everything by hand
fn update_db_port_manual(config: &AppConfig, new_port: u16) -> AppConfig {
    AppConfig {
        server: ServerConfig {
            db: DbConfig {
                port: new_port,
                ..config.server.db.clone()
            },
            ..config.server.clone()
        },
        ..config.clone()
    }
}

// Approach 2: Helper functions — map at each level
fn map_server(f: impl FnOnce(ServerConfig) -> ServerConfig, config: &AppConfig) -> AppConfig {
    AppConfig {
        server: f(config.server.clone()),
        ..config.clone()
    }
}

fn map_db(f: impl FnOnce(DbConfig) -> DbConfig, server: ServerConfig) -> ServerConfig {
    ServerConfig {
        db: f(server.db.clone()),
        ..server
    }
}

fn set_port(port: u16, db: DbConfig) -> DbConfig {
    DbConfig { port, ..db }
}

fn update_db_port_helpers(config: &AppConfig, new_port: u16) -> AppConfig {
    map_server(|s| map_db(|d| set_port(new_port, d), s), config)
}

// Approach 3: Lenses — composable getters and setters
struct Lens<S, A> {
    get: Box<dyn Fn(&S) -> A>,
    set: Box<dyn Fn(A, &S) -> S>,
}

impl<S: 'static, A: 'static> Lens<S, A> {
    fn new(
        get: impl Fn(&S) -> A + 'static,
        set: impl Fn(A, &S) -> S + 'static,
    ) -> Self {
        Lens {
            get: Box::new(get),
            set: Box::new(set),
        }
    }

    fn compose<B: 'static>(self, inner: Lens<A, B>) -> Lens<S, B>
    where
        A: Clone,
        S: Clone,
    {
        use std::rc::Rc;
        let outer_get: Rc<dyn Fn(&S) -> A> = Rc::from(self.get);
        let outer_set = self.set;
        let inner_get = inner.get;
        let inner_set = inner.set;
        let og1 = outer_get.clone();
        let og2 = outer_get;
        Lens {
            get: Box::new(move |s| (inner_get)(&(og1)(s))),
            set: Box::new(move |b, s| {
                let a = (og2)(s);
                let new_a = (inner_set)(b, &a);
                (outer_set)(new_a, s)
            }),
        }
    }
}

fn server_lens() -> Lens<AppConfig, ServerConfig> {
    Lens::new(
        |c: &AppConfig| c.server.clone(),
        |s: ServerConfig, c: &AppConfig| AppConfig { server: s, ..c.clone() },
    )
}

fn db_lens() -> Lens<ServerConfig, DbConfig> {
    Lens::new(
        |s: &ServerConfig| s.db.clone(),
        |d: DbConfig, s: &ServerConfig| ServerConfig { db: d, ..s.clone() },
    )
}

fn port_lens() -> Lens<DbConfig, u16> {
    Lens::new(
        |d: &DbConfig| d.port,
        |p: u16, d: &DbConfig| DbConfig { port: p, ..d.clone() },
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    fn sample_config() -> AppConfig {
        AppConfig {
            server: ServerConfig {
                db: DbConfig {
                    host: "localhost".into(),
                    port: 5432,
                    name: "mydb".into(),
                },
                max_connections: 100,
            },
            debug: false,
            version: "1.0".into(),
        }
    }

    #[test]
    fn test_manual_update() {
        let c = update_db_port_manual(&sample_config(), 5433);
        assert_eq!(c.server.db.port, 5433);
        assert_eq!(c.server.max_connections, 100);
    }

    #[test]
    fn test_helper_update() {
        let c = update_db_port_helpers(&sample_config(), 5433);
        assert_eq!(c.server.db.port, 5433);
    }

    #[test]
    fn test_lens_update() {
        let lens = server_lens().compose(db_lens()).compose(port_lens());
        let c = (lens.set)(5433, &sample_config());
        assert_eq!((lens.get)(&c), 5433);
        assert_eq!(c.server.max_connections, 100);
    }

    #[test]
    fn test_all_equivalent() {
        let cfg = sample_config();
        let c1 = update_db_port_manual(&cfg, 9999);
        let c2 = update_db_port_helpers(&cfg, 9999);
        let lens = server_lens().compose(db_lens()).compose(port_lens());
        let c3 = (lens.set)(9999, &cfg);
        assert_eq!(c1, c2);
        assert_eq!(c2, c3);
    }
}
(* Example 201: The Nested Update Problem — Why Lenses Exist *)

(* === The Problem: Deeply Nested Record Updates === *)

type db_config = {
  host : string;
  port : int;
  name : string;
}

type server_config = {
  db : db_config;
  max_connections : int;
}

type app_config = {
  server : server_config;
  debug : bool;
  version : string;
}

(* Approach 1: Manual nested update — the pain *)
let update_db_port_manual config new_port =
  { config with
    server = { config.server with
      db = { config.server.db with
        port = new_port
      }
    }
  }

(* Look at that nesting! And it gets worse with deeper structures. *)

(* Approach 2: Helper functions for each level *)
let map_server f config = { config with server = f config.server }
let map_db f server = { server with db = f server.db }
let set_port port db = { db with port }

let update_db_port_helpers config new_port =
  config |> map_server (map_db (set_port new_port))

(* Better! But we need a helper for every field at every level. *)

(* Approach 3: Lenses — composable getters and setters *)
type ('s, 'a) lens = {
  get : 's -> 'a;
  set : 'a -> 's -> 's;
}

let compose outer inner = {
  get = (fun s -> inner.get (outer.get s));
  set = (fun a s -> outer.set (inner.set a (outer.get s)) s);
}

let server_lens = {
  get = (fun c -> c.server);
  set = (fun s c -> { c with server = s });
}

let db_lens = {
  get = (fun s -> s.db);
  set = (fun d s -> { s with db = d });
}

let port_lens = {
  get = (fun d -> d.port);
  set = (fun p d -> { d with port = p });
}

(* Compose to zoom all the way in *)
let app_db_port = compose (compose server_lens db_lens) port_lens

let update_db_port_lens config new_port =
  app_db_port.set new_port config

(* Now ANY depth is just composition! *)

(* === Tests === *)
let () =
  let config = {
    server = {
      db = { host = "localhost"; port = 5432; name = "mydb" };
      max_connections = 100;
    };
    debug = false;
    version = "1.0";
  } in

  (* Test manual *)
  let c1 = update_db_port_manual config 5433 in
  assert (c1.server.db.port = 5433);
  assert (c1.server.max_connections = 100);
  assert (c1.debug = false);

  (* Test helpers *)
  let c2 = update_db_port_helpers config 5433 in
  assert (c2.server.db.port = 5433);

  (* Test lens *)
  let c3 = update_db_port_lens config 5433 in
  assert (c3.server.db.port = 5433);
  assert (app_db_port.get c3 = 5433);

  (* All three produce same result *)
  assert (c1 = c2);
  assert (c2 = c3);

  print_endline "✓ All tests passed"

📊 Detailed Comparison

Comparison: Example 201 — The Nested Update Problem

The Pain: Manual Nested Update

OCaml

🐪 Show OCaml equivalent
let update_db_port config new_port =
{ config with
 server = { config.server with
   db = { config.server.db with
     port = new_port } } }

Rust

fn update_db_port(config: &AppConfig, new_port: u16) -> AppConfig {
 AppConfig {
     server: ServerConfig {
         db: DbConfig { port: new_port, ..config.server.db.clone() },
         ..config.server.clone()
     },
     ..config.clone()
 }
}

The Solution: Lens Type

OCaml

🐪 Show OCaml equivalent
type ('s, 'a) lens = {
get : 's -> 'a;
set : 'a -> 's -> 's;
}

let compose outer inner = {
get = (fun s -> inner.get (outer.get s));
set = (fun a s -> outer.set (inner.set a (outer.get s)) s);
}

Rust

struct Lens<S, A> {
 get: Box<dyn Fn(&S) -> A>,
 set: Box<dyn Fn(A, &S) -> S>,
}

impl<S: 'static, A: 'static> Lens<S, A> {
 fn compose<B: 'static>(self, inner: Lens<A, B>) -> Lens<S, B>
 where A: Clone, S: Clone {
     // ... chains get/set through both levels
 }
}

Usage Comparison

OCaml

🐪 Show OCaml equivalent
let app_db_port = compose (compose server_lens db_lens) port_lens
let new_config = app_db_port.set 5433 config

Rust

let app_db_port = server_lens().compose(db_lens()).compose(port_lens());
let new_config = (app_db_port.set)(5433, &config);