🦀 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

203: Lens Laws — What Makes a Lens Well-Behaved

Difficulty: ⭐⭐⭐ Level: Intermediate Three simple laws separate a trustworthy Lens from one that will silently corrupt your data.

The Problem This Solves

A Lens is just two functions. Nothing stops you from writing a `set` function that secretly mutates unrelated fields, or a `get` that returns a transformed value that doesn't round-trip back. Your code will compile. Your tests might even pass — until you compose the Lens with another and the silent transformation accumulates somewhere unexpected. Without laws, Lens composition becomes a minefield. You can't confidently say "I composed three Lenses; the result is a correct accessor for the deeply nested field." You'd have to inspect every intermediate implementation. Laws are contracts that enable composition to be trustworthy. When every Lens in your system satisfies the three laws, you can compose them freely and the resulting Lens is always correct. Without laws, composition is gambling. This example exists to solve exactly that pain.

The Intuition

Think of a Lens as a window into a struct. A well-behaved window has three properties: 1. GetSet — "Looking through the window and then closing it leaves the room unchanged." If you `get` a value and immediately `set` it back, the struct is identical to the original.
set(get(s), s) == s
2. SetGet — "What you put in is what you see." If you `set` a value and then `get` it back, you get exactly what you set.
get(set(a, s)) == a
3. SetSet — "The last write wins." Setting twice is the same as setting once with the last value — the first set is fully overwritten.
set(b, set(a, s)) == set(b, s)
An unlawful Lens violates at least one of these. The bad example in this file sets `x` but also increments `y` as a side effect — this breaks GetSet immediately because setting `x` to what you just read still changes the struct.

How It Works in Rust

The lawful Lens — `x_lens`:
fn x_lens() -> Lens<Point, f64> {
 Lens::new(
     |p| p.x,
     |x, p| Point { x, ..p.clone() },   // only x changes
 )
}
The unlawful Lens — `bad_lens`:
fn bad_lens() -> Lens<Point, f64> {
 Lens::new(
     |p| p.x,
     |x, p| Point { x, y: p.y + 1.0 },  // set silently mutates y!
 )
}
Verifying the laws as generic functions:
// Law 1: GetSet — set what you got changes nothing
fn check_get_set<S: PartialEq + Clone, A: Clone>(lens: &Lens<S, A>, s: &S) -> bool {
 let a = (lens.get)(s);
 (lens.set)(a, s) == *s
}

// Law 2: SetGet — get what you set
fn check_set_get<S: Clone, A: PartialEq + Clone>(lens: &Lens<S, A>, a: A, s: &S) -> bool {
 (lens.get)(&(lens.set)(a.clone(), s)) == a
}

// Law 3: SetSet — last write wins
fn check_set_set<S: PartialEq + Clone, A: Clone>(
 lens: &Lens<S, A>, a: A, b: A, s: &S,
) -> bool {
 (lens.set)(b.clone(), &(lens.set)(a, s)) == (lens.set)(b, s)
}
These checkers are generic — they work with any `Lens<S, A>` where `S: PartialEq + Clone` and `A: PartialEq + Clone`. You can use them as property tests or in unit tests with specific values. Running the checks:
let p = Point { x: 3.0, y: 4.0 };

// x_lens passes all three laws
assert!(check_get_set(&x_lens(), &p));
assert!(check_set_get(&x_lens(), 10.0, &p));
assert!(check_set_set(&x_lens(), 10.0, 20.0, &p));

// bad_lens fails GetSet immediately
assert!(!check_get_set(&bad_lens(), &p));

// Inspect why: set(get(p), p) changed y
let bl = bad_lens();
let p2 = (bl.set)((bl.get)(&p), &p);
assert_eq!(p2.y, 5.0);  // was 4.0 — bad_lens mutated it!

What This Unlocks

Key Differences

ConceptOCamlRust
Equality checkStructural equality by defaultRequires `#[derive(PartialEq)]`
Law checker signature`'a lens -> 's -> bool``fn check_get_set<S: PartialEq+Clone, A: Clone>`
Float comparisonStructural `=` works`f64: PartialEq` — works, but watch NaN
Batch verification`List.for_all check values`Iterator `.all(…)` or a loop
Bad Lens exampleMutate `y` in `set` on `x`Same pattern — `y: p.y + 1.0` in the `set` closure
// Example 203: Lens Laws — GetSet, SetGet, SetSet

#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: f64,
    y: f64,
}

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

impl<S, A> 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) }
    }
}

// Approach 1: Lawful lenses
fn x_lens() -> Lens<Point, f64> {
    Lens::new(|p: &Point| p.x, |x: f64, p: &Point| Point { x, ..p.clone() })
}

fn y_lens() -> Lens<Point, f64> {
    Lens::new(|p: &Point| p.y, |y: f64, p: &Point| Point { y, ..p.clone() })
}

// Approach 2: An UNLAWFUL lens — set has a side effect
fn bad_lens() -> Lens<Point, f64> {
    Lens::new(
        |p: &Point| p.x,
        |x: f64, p: &Point| Point { x, y: p.y + 1.0 }, // mutates y!
    )
}

// Approach 3: Law verification
fn check_get_set<S: PartialEq + Clone, A: Clone>(lens: &Lens<S, A>, s: &S) -> bool {
    let a = (lens.get)(s);
    let result = (lens.set)(a, s);
    result == *s
}

fn check_set_get<S: Clone, A: PartialEq + Clone>(lens: &Lens<S, A>, a: A, s: &S) -> bool {
    let result = (lens.get)(&(lens.set)(a.clone(), s));
    result == a
}

fn check_set_set<S: PartialEq + Clone, A: Clone>(
    lens: &Lens<S, A>, a: A, b: A, s: &S,
) -> bool {
    let r1 = (lens.set)(b.clone(), &(lens.set)(a, s));
    let r2 = (lens.set)(b, s);
    r1 == r2
}

fn verify_laws<S: PartialEq + Clone, A: PartialEq + Clone>(
    name: &str, lens: &Lens<S, A>, s: &S, a: A, b: A,
) -> (bool, bool, bool) {
    let gs = check_get_set(lens, s);
    let sg = check_set_get(lens, a.clone(), s);
    let ss = check_set_set(lens, a, b, s);
    println!("{}: GetSet={} SetGet={} SetSet={}", name, gs, sg, ss);
    (gs, sg, ss)
}

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

    #[test]
    fn test_x_lens_lawful() {
        let p = Point { x: 1.0, y: 2.0 };
        let l = x_lens();
        assert!(check_get_set(&l, &p));
        assert!(check_set_get(&l, 99.0, &p));
        assert!(check_set_set(&l, 5.0, 10.0, &p));
    }

    #[test]
    fn test_bad_lens_unlawful() {
        let p = Point { x: 1.0, y: 2.0 };
        let l = bad_lens();
        assert!(!check_get_set(&l, &p));
    }

    #[test]
    fn test_set_get_law() {
        let p = Point { x: 0.0, y: 0.0 };
        let l = y_lens();
        let result = (l.get)(&(l.set)(42.0, &p));
        assert_eq!(result, 42.0);
    }

    #[test]
    fn test_set_set_law() {
        let p = Point { x: 1.0, y: 2.0 };
        let l = x_lens();
        let r1 = (l.set)(99.0, &(l.set)(50.0, &p));
        let r2 = (l.set)(99.0, &p);
        assert_eq!(r1, r2);
    }
}
(* Example 203: Lens Laws — GetSet, SetGet, SetSet *)

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

(* The three lens laws:
   1. GetSet: set (get s) s = s        — setting what you got changes nothing
   2. SetGet: get (set a s) = a        — you get back what you set
   3. SetSet: set b (set a s) = set b s — setting twice = setting last value *)

type point = { x : float; y : float }

(* Approach 1: A lawful lens *)
let x_lens : (point, float) lens = {
  get = (fun p -> p.x);
  set = (fun x p -> { p with x });
}

let y_lens : (point, float) lens = {
  get = (fun p -> p.y);
  set = (fun y p -> { p with y });
}

(* Approach 2: An UNLAWFUL lens — to show what goes wrong *)
let bad_lens : (point, float) lens = {
  get = (fun p -> p.x);
  set = (fun x p -> { x; y = p.y +. 1.0 }); (* side effect! mutates y *)
}

(* Approach 3: Law verification functions *)
let check_get_set lens s =
  let result = lens.set (lens.get s) s in
  result = s

let check_set_get lens a s =
  let result = lens.get (lens.set a s) in
  result = a

let check_set_set lens a b s =
  let r1 = lens.set b (lens.set a s) in
  let r2 = lens.set b s in
  r1 = r2

let verify_laws name lens s a b =
  let gs = check_get_set lens s in
  let sg = check_set_get lens a s in
  let ss = check_set_set lens a b s in
  Printf.printf "%s: GetSet=%b SetGet=%b SetSet=%b\n" name gs sg ss;
  (gs, sg, ss)

(* === Tests === *)
let () =
  let p = { x = 3.0; y = 4.0 } in

  (* x_lens is lawful *)
  let (gs, sg, ss) = verify_laws "x_lens" x_lens p 10.0 20.0 in
  assert gs; assert sg; assert ss;

  (* y_lens is lawful *)
  let (gs, sg, ss) = verify_laws "y_lens" y_lens p 10.0 20.0 in
  assert gs; assert sg; assert ss;

  (* bad_lens violates GetSet *)
  let (gs, _sg, _ss) = verify_laws "bad_lens" bad_lens p 10.0 20.0 in
  assert (not gs); (* GetSet fails: set changes y! *)

  (* Verify the specific violations *)
  let p2 = bad_lens.set (bad_lens.get p) p in
  assert (p2.y = 5.0); (* y was mutated! *)
  assert (p2 <> p);

  print_endline "✓ All tests passed"

📊 Detailed Comparison

Comparison: Example 203 — Lens Laws

Law Definitions

OCaml

🐪 Show OCaml equivalent
(* GetSet: set (get s) s = s *)
let check_get_set lens s =
lens.set (lens.get s) s = s

(* SetGet: get (set a s) = a *)
let check_set_get lens a s =
lens.get (lens.set a s) = a

(* SetSet: set b (set a s) = set b s *)
let check_set_set lens a b s =
lens.set b (lens.set a s) = lens.set b s

Rust

fn check_get_set<S: PartialEq + Clone, A: Clone>(lens: &Lens<S, A>, s: &S) -> bool {
 let a = (lens.get)(s);
 (lens.set)(a, s) == *s
}

fn check_set_get<S: Clone, A: PartialEq + Clone>(lens: &Lens<S, A>, a: A, s: &S) -> bool {
 (lens.get)(&(lens.set)(a.clone(), s)) == a
}

fn check_set_set<S: PartialEq + Clone, A: Clone>(lens: &Lens<S, A>, a: A, b: A, s: &S) -> bool {
 (lens.set)(b.clone(), &(lens.set)(a, s)) == (lens.set)(b, s)
}

Unlawful Lens

OCaml

🐪 Show OCaml equivalent
let bad_lens = {
get = (fun p -> p.x);
set = (fun x p -> { x; y = p.y +. 1.0 }); (* side effect! *)
}

Rust

fn bad_lens() -> Lens<Point, f64> {
 Lens::new(|p| p.x, |x, p| Point { x, y: p.y + 1.0 }) // side effect!
}