๐Ÿฆ€ Functional Rust

422: Derive Macros: Concept and Usage

Difficulty: 3 Level: Advanced Demystify `#[derive(Debug)]` by showing what code it actually generates โ€” and understand when to use derive vs write implementations by hand.

The Problem This Solves

Every Rust beginner writes `#[derive(Debug, Clone, PartialEq)]` and it magically works. But when something goes wrong โ€” a custom type doesn't derive, a newtype wraps a non-`Hash` type, an enum variant breaks `Ord` ordering โ€” you're stuck. You can't debug what you don't understand. Beyond debugging, derive macros are the entry point to a deeper capability: procedural macros. `#[derive(Serialize)]` from serde, `#[derive(Error)]` from thiserror, `#[derive(Component)]` from Bevy โ€” all of these are proc macros that generate substantial code from a struct definition. Understanding what `#[derive(Debug)]` actually emits teaches you to reason about what any derive macro might emit. The manual equivalents also matter in practice. You'll hand-write `PartialEq` when two structs are equal only when a subset of fields match, or `Debug` when you want to redact a password field. Knowing what derive would have generated makes writing the custom version straightforward.

The Intuition

`#[derive(Debug)]` is a code generator. Before compilation, the Rust compiler hands the macro your struct definition and says "generate the `Debug` impl." The macro produces something like what you'd write yourself โ€” `f.debug_struct("Point").field("x", &self.x).field("y", &self.y).finish()` โ€” and that generated code is compiled alongside your own. All standard derive traits follow the same pattern: they inspect each field or variant and compose the implementation recursively. `#[derive(Clone)]` calls `.clone()` on each field. `#[derive(PartialEq)]` compares fields pairwise. `#[derive(Ord)]` compares fields left to right, using the first non-equal result.

How It Works in Rust

// What you write:
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
struct Point { x: i32, y: i32 }

// What #[derive(Debug)] generates (simplified):
impl fmt::Debug for Point {
 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     f.debug_struct("Point")
         .field("x", &self.x)   // calls Debug on each field
         .field("y", &self.y)
         .finish()
 }
}

// What #[derive(PartialEq)] generates:
impl PartialEq for Point {
 fn eq(&self, other: &Self) -> bool {
     self.x == other.x && self.y == other.y
 }
}

// What #[derive(Default)] generates:
impl Default for Point {
 fn default() -> Self {
     Point { x: i32::default(), y: i32::default() }
 }
}
Practical outcomes of derived traits:

What This Unlocks

Key Differences

ConceptOCamlRust
Code generation from type`ppx_deriving` (show, eq, ord) โ€” third-party, requires opam pkg`#[derive(...)]` โ€” built into compiler, no deps for std traits
Manual equivalentWrite `show_point`, `equal_point` functionsImplement `fmt::Debug`, `PartialEq` traits
Syntax`[@@deriving show, eq]` attribute`#[derive(Debug, PartialEq)]` attribute
Ordering`compare` polymorphic function`PartialOrd` + `Ord` traits; field order matters for derived `Ord`
Hash map keyAny type with structural equalityRequires `Hash + Eq` both derived or both implemented
// Derive macros: concept and usage in Rust

// All standard derivable traits
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
struct Point {
    x: i32,
    y: i32,
}

// Enum derivations
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum Shape {
    Circle { radius: u32 },
    Rectangle { width: u32, height: u32 },
    Triangle { base: u32, height: u32 },
}

// What derive actually generates โ€” shown manually for Point
// (This is equivalent to #[derive(Debug)] for Point)
mod manual_impls {
    use super::Point;
    use std::fmt;

    // What #[derive(Debug)] generates:
    impl fmt::Debug for Point {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            f.debug_struct("Point")
                .field("x", &self.x)
                .field("y", &self.y)
                .finish()
        }
    }
}

// Using derived traits in practice
use std::collections::{HashMap, HashSet, BTreeSet};

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 1, y: 2 };
    let p3 = Point { x: 3, y: 4 };

    // Debug
    println!("{:?}", p1);
    println!("{:#?}", p1); // pretty-print

    // PartialEq / Eq
    println!("p1 == p2: {}", p1 == p2);
    println!("p1 == p3: {}", p1 == p3);

    // Ord โ€” sorting
    let mut points = vec![p3.clone(), p1.clone(), p2.clone()];
    points.sort();
    println!("Sorted: {:?}", points);

    // Hash โ€” use as HashMap key
    let mut map: HashMap<Point, String> = HashMap::new();
    map.insert(p1.clone(), "origin-ish".to_string());
    println!("Map lookup: {:?}", map[&p1]);

    // Clone
    let p4 = p1.clone();
    println!("Cloned: {:?}", p4);

    // Default
    let default_p = Point::default();
    println!("Default: {:?}", default_p);

    // Enum derive
    let shapes = vec![
        Shape::Circle { radius: 5 },
        Shape::Rectangle { width: 3, height: 4 },
    ];
    let mut btree: BTreeSet<Shape> = shapes.into_iter().collect();
    btree.insert(Shape::Triangle { base: 3, height: 4 });
    println!("Shapes (sorted): {:?}", btree);
}

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

    #[test]
    fn test_derived_eq() {
        let p1 = Point { x: 1, y: 2 };
        let p2 = Point { x: 1, y: 2 };
        assert_eq!(p1, p2);
    }

    #[test]
    fn test_derived_ord() {
        let mut v = vec![Point { x: 3, y: 0 }, Point { x: 1, y: 0 }];
        v.sort();
        assert_eq!(v[0], Point { x: 1, y: 0 });
    }

    #[test]
    fn test_derived_clone() {
        let p = Point { x: 5, y: 6 };
        let q = p.clone();
        assert_eq!(p, q);
    }

    #[test]
    fn test_derived_default() {
        let p = Point::default();
        assert_eq!(p.x, 0);
        assert_eq!(p.y, 0);
    }
}
(* Derive macro concepts in OCaml *)
(* OCaml show and eq from ppx_deriving *)

(* Without ppx: manual implementations *)
type point = { x: float; y: float }

(* Manual "derive Debug" *)
let show_point {x; y} = Printf.sprintf "Point { x = %g; y = %g }" x y

(* Manual "derive Eq" *)
let equal_point a b = a.x = b.x && a.y = b.y

(* Manual "derive Ord" *)
let compare_point a b =
  let cx = compare a.x b.x in
  if cx <> 0 then cx else compare a.y b.y

(* With ppx_deriving, you'd write: *)
(* type point = { x: float; y: float } [@@deriving show, eq, ord] *)

type shape = Circle of float | Rectangle of float * float | Triangle of float * float * float

let show_shape = function
  | Circle r -> Printf.sprintf "Circle(%g)" r
  | Rectangle (w, h) -> Printf.sprintf "Rectangle(%g, %g)" w h
  | Triangle (a, b, c) -> Printf.sprintf "Triangle(%g, %g, %g)" a b c

let () =
  let p1 = {x = 1.0; y = 2.0} in
  let p2 = {x = 1.0; y = 2.0} in
  let p3 = {x = 3.0; y = 4.0} in
  Printf.printf "%s\n" (show_point p1);
  Printf.printf "p1 = p2: %b\n" (equal_point p1 p2);
  Printf.printf "p1 = p3: %b\n" (equal_point p1 p3);
  Printf.printf "compare: %d\n" (compare_point p1 p3);
  List.iter (fun s -> Printf.printf "%s\n" (show_shape s))
    [Circle 5.0; Rectangle 3.0 4.0; Triangle 3.0 4.0 5.0]