🦀 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

202: Lens Basics — View, Set, and Over

Difficulty: ⭐⭐⭐ Level: Intermediate A Lens bundles a getter and setter into one reusable value, and three operations — `view`, `set`, `over` — cover every access pattern you need.

The Problem This Solves

You're writing a function that updates a field on a struct. You pass the struct in, return a new one. Straightforward. But now you need the same pattern for five different fields across three different structs. You end up writing fifteen near-identical functions: `update_name`, `update_age`, `update_address_city`, and so on. This is brittle. When your struct gains a field, every update function might need touching. When you want to apply a transformation rather than a hard set — like "increment the age" — you add another fifteen functions: `increment_age`, `double_salary`, etc. The root problem is that field access isn't first-class in Rust. You can't pass "the name field of Person" as a value — you have to write a function that hard-codes the field name. This prevents abstraction. A Lens makes field access first-class. You define the getter and setter once, name the Lens, and then `view`, `set`, and `over` cover every operation without writing new functions. One Lens replaces a whole family of `update_*` helpers. This example exists to solve exactly that pain.

The Intuition

There are three things you ever want to do with a field: 1. Read it — give me the value at this field (`view`) 2. Replace it — give me a new struct with this field changed to X (`set`) 3. Transform it — give me a new struct where this field has been run through function F (`over`) `over` is not a bonus — it's the most useful of the three. "Increment the counter", "uppercase the name", "double the salary" are all `over`.
view  lens s       =>  A              (read the value)
set   lens a s     =>  S              (replace with a)
over  lens f s     =>  S              (transform with f)
`over` is just `set(f(view(s)), s)` — so only `get` and `set` are fundamental. But `over` is what you'll actually use. The Lens itself is just two functions stored together:
struct Lens<S, A> {
 get: Box<dyn Fn(&S) -> A>,   // S = the struct, A = the field type
 set: Box<dyn Fn(A, &S) -> S>,
}

How It Works in Rust

Defining a Lens and using all three operations:
fn name_lens() -> Lens<Person, String> {
 Lens::new(
     |p| p.name.clone(),                             // get
     |n, p| Person { name: n, ..p.clone() },         // set
 )
}

let alice = Person { name: "Alice".into(), age: 30 };
let nl = name_lens();

// view: read the field
assert_eq!(nl.view(&alice), "Alice");

// set: replace the field
let alice2 = nl.set("Alicia".into(), &alice);
assert_eq!(nl.view(&alice2), "Alicia");

// over: transform the field
let upper = nl.over(|n| n.to_uppercase(), &alice);
assert_eq!(nl.view(&upper), "ALICE");
Trait-based Lenses (zero-cost, no heap allocation): The closure approach uses `Box<dyn Fn>` which allocates. For hot paths, use a trait instead:
trait LensLike<S, A> {
 fn get(s: &S) -> A;
 fn set(a: A, s: &S) -> S;
 fn over(f: impl FnOnce(A) -> A, s: &S) -> S {
     let a = Self::get(s);
     Self::set(f(a), s)
 }
}
Each Lens is now a zero-sized type — the compiler monomorphizes everything, no runtime dispatch, no allocation. Macro-generated Lenses (cut the boilerplate):
make_lens!(PersonName, Person, name, String);
make_lens!(PersonAge,  Person, age,  u32);

// Now use directly as associated functions:
PersonName::get(&alice);                    // "Alice"
PersonAge::over(|a| a + 1, &alice);        // Person { age: 31, .. }
The macro generates the zero-sized struct and the `LensLike` impl — one line per field.

What This Unlocks

Key Differences

ConceptOCamlRust
Lens typeRecord of two functionsStruct with `Box<dyn Fn>` or a trait
Zero-cost optionNo (closures use GC heap)Yes — trait-based, fully monomorphized
`over` / `modify`Simple record combinatorMethod on `Lens<S,A>` or trait default method
Boilerplate per fieldMinimal — 2 linesMacro helps; still more verbose
Polymorphic updateImplicit with parametric typesRequires explicit `Clone` bounds
// Example 202: Lens Basics — Lens as a Pair of Get and Set

// === Approach 1: Lens as struct with closures === //

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 view(&self, s: &S) -> A {
        (self.get)(s)
    }

    fn set(&self, a: A, s: &S) -> S {
        (self.set)(a, s)
    }

    fn over(&self, f: impl FnOnce(A) -> A, s: &S) -> S {
        let a = (self.get)(s);
        (self.set)(f(a), s)
    }
}

// === Approach 2: Lens via trait (zero-cost abstraction) === //

trait LensLike<S, A> {
    fn get(s: &S) -> A;
    fn set(a: A, s: &S) -> S;

    fn over(f: impl FnOnce(A) -> A, s: &S) -> S {
        let a = Self::get(s);
        Self::set(f(a), s)
    }
}

// === Approach 3: Macro-generated lenses === //

macro_rules! make_lens {
    ($lens_name:ident, $struct:ty, $field:ident, $field_ty:ty) => {
        struct $lens_name;
        impl LensLike<$struct, $field_ty> for $lens_name {
            fn get(s: &$struct) -> $field_ty { s.$field.clone() }
            fn set(a: $field_ty, s: &$struct) -> $struct {
                let mut new = s.clone();
                new.$field = a;
                new
            }
        }
    };
}

#[derive(Debug, Clone, PartialEq)]
struct Person {
    name: String,
    age: u32,
}

#[derive(Debug, Clone, PartialEq)]
struct Address {
    street: String,
    city: String,
    zip: String,
}

#[derive(Debug, Clone, PartialEq)]
struct Employee {
    emp_name: String,
    address: Address,
}

// Generate lenses via macro
make_lens!(PersonName, Person, name, String);
make_lens!(PersonAge, Person, age, u32);
make_lens!(EmpAddress, Employee, address, Address);
make_lens!(AddrCity, Address, city, String);

fn name_lens() -> Lens<Person, String> {
    Lens::new(|p: &Person| p.name.clone(), |n: String, p: &Person| Person { name: n, ..p.clone() })
}

fn age_lens() -> Lens<Person, u32> {
    Lens::new(|p: &Person| p.age, |a: u32, p: &Person| Person { age: a, ..p.clone() })
}

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

    #[test]
    fn test_closure_lens_get_set() {
        let p = Person { name: "Bob".into(), age: 25 };
        let nl = name_lens();
        assert_eq!(nl.view(&p), "Bob");
        let p2 = nl.set("Robert".into(), &p);
        assert_eq!(nl.view(&p2), "Robert");
        assert_eq!(p2.age, 25); // other fields unchanged
    }

    #[test]
    fn test_trait_lens() {
        let p = Person { name: "Eve".into(), age: 40 };
        assert_eq!(PersonAge::get(&p), 40);
        let p2 = PersonAge::set(41, &p);
        assert_eq!(PersonAge::get(&p2), 41);
    }

    #[test]
    fn test_over_modify() {
        let p = Person { name: "X".into(), age: 10 };
        let p2 = PersonAge::over(|a| a * 2, &p);
        assert_eq!(PersonAge::get(&p2), 20);
    }

    #[test]
    fn test_macro_lens_for_nested() {
        let emp = Employee {
            emp_name: "Charlie".into(),
            address: Address { street: "1st".into(), city: "NYC".into(), zip: "10001".into() },
        };
        let addr = EmpAddress::get(&emp);
        assert_eq!(AddrCity::get(&addr), "NYC");
    }
}
(* Example 202: Lens Basics — Lens as a Pair of Get and Set *)

(* A lens focuses on one part of a larger structure *)
type ('s, 'a) lens = {
  get : 's -> 'a;
  set : 'a -> 's -> 's;
}

(* === Approach 1: Manual lens construction === *)

type person = {
  name : string;
  age : int;
}

let name_lens : (person, string) lens = {
  get = (fun p -> p.name);
  set = (fun n p -> { p with name = n });
}

let age_lens : (person, int) lens = {
  get = (fun p -> p.age);
  set = (fun a p -> { p with age = a });
}

(* Use a lens *)
let view l s = l.get s
let set l a s = l.set a s
let over l f s = l.set (f (l.get s)) s

(* === Approach 2: Lens via functor (more general) === *)
module type FUNCTOR = sig
  type 'a t
  val map : ('a -> 'b) -> 'a t -> 'b t
end

(* Identity functor for get *)
module Identity = struct
  type 'a t = Id of 'a
  let map f (Id x) = Id (f x)
  let run (Id x) = x
end

(* Const functor for set *)
module Const = struct
  type ('a, 'b) t = Const of 'a
  let map _ (Const x) = Const x
  let run (Const x) = x
end

(* === Approach 3: Lens for nested types === *)

type address = {
  street : string;
  city : string;
  zip : string;
}

type employee = {
  emp_name : string;
  address : address;
}

let address_lens : (employee, address) lens = {
  get = (fun e -> e.address);
  set = (fun a e -> { e with address = a });
}

let street_lens : (address, string) lens = {
  get = (fun a -> a.street);
  set = (fun s a -> { a with street = s });
}

let city_lens : (address, string) lens = {
  get = (fun a -> a.city);
  set = (fun c a -> { a with city = c });
}

(* === Tests === *)
let () =
  let alice = { name = "Alice"; age = 30 } in

  (* Test get *)
  assert (view name_lens alice = "Alice");
  assert (view age_lens alice = 30);

  (* Test set *)
  let alice2 = set name_lens "Alicia" alice in
  assert (view name_lens alice2 = "Alicia");
  assert (view age_lens alice2 = 30);

  (* Test over (modify) *)
  let alice3 = over age_lens (fun a -> a + 1) alice in
  assert (view age_lens alice3 = 31);

  (* Test with nested types *)
  let emp = {
    emp_name = "Bob";
    address = { street = "123 Main St"; city = "Springfield"; zip = "62701" };
  } in
  assert (view address_lens emp |> view city_lens = "Springfield");

  let emp2 = set address_lens { (view address_lens emp) with city = "Shelbyville" } emp in
  assert (view address_lens emp2 |> view city_lens = "Shelbyville");

  print_endline "✓ All tests passed"

📊 Detailed Comparison

Comparison: Example 202 — Lens Basics

Lens Type Definition

OCaml

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

let view l s = l.get s
let set l a s = l.set a s
let over l f s = l.set (f (l.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 view(&self, s: &S) -> A { (self.get)(s) }
 fn set(&self, a: A, s: &S) -> S { (self.set)(a, s) }
 fn over(&self, f: impl FnOnce(A) -> A, s: &S) -> S {
     (self.set)(f((self.get)(s)), s)
 }
}

Creating a Lens

OCaml

🐪 Show OCaml equivalent
let name_lens = {
get = (fun p -> p.name);
set = (fun n p -> { p with name = n });
}

Rust (Closure)

fn name_lens() -> Lens<Person, String> {
 Lens::new(|p| p.name.clone(), |n, p| Person { name: n, ..p.clone() })
}

Rust (Trait — zero-cost)

struct PersonName;
impl LensLike<Person, String> for PersonName {
 fn get(s: &Person) -> String { s.name.clone() }
 fn set(a: String, s: &Person) -> Person {
     Person { name: a, ..s.clone() }
 }
}