RustDerive Macros

Derive Macros in Rust

The #[derive(...)] attribute tells the Rust compiler to automatically generate a trait implementation for your type. Instead of writing dozens of lines of boilerplate, you list the traits you want and Rust writes the code for you — at compile time, with zero runtime overhead.

This page covers all standard derivable traits and when to use them.

How Derive Works

#[derive(...)] is a proc-macro (procedural macro) that runs at compile time. It reads the definition of your type, generates the appropriate impl block, and inserts it into the compiled output. The generated code is exactly what you would write by hand — derive just automates the mechanical parts.

One key requirement: every field (or variant) in your type must itself implement the derived trait, or the compiler will refuse.

RUST
// Without derive — manual boilerplate
struct Point { x: i32, y: i32 }

impl std::fmt::Debug for Point {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Point")
         .field("x", &self.x)
         .field("y", &self.y)
         .finish()
    }
}

// With derive — equivalent output, zero boilerplate
#[derive(Debug)]
struct PointDerived { x: i32, y: i32 }

fn main() {
    let p = PointDerived { x: 3, y: 7 };
    println!("{:?}", p);  // PointDerived { x: 3, y: 7 }
    println!("{:#?}", p); // pretty-printed
}
PointDerived { x: 3, y: 7 }
PointDerived {
    x: 3,
    y: 7,
}
Debug

Debug enables {:?} and {:#?} formatting. It is used everywhere — in println!, dbg!, test failure output, and error messages. Nearly every type you create should derive Debug.

RUST
#[derive(Debug)]
struct User {
    id:    u32,
    name:  String,
    admin: bool,
}

#[derive(Debug)]
enum Role { Guest, Editor, Admin }

fn main() {
    let user = User { id: 1, name: String::from("Alice"), admin: true };
    println!("{:?}",  user);   // compact
    println!("{:#?}", user);   // pretty-printed

    // dbg! prints file/line and the value, then returns it
    let role = dbg!(Role::Admin);
    println!("{:?}", role);
}
User { id: 1, name: "Alice", admin: true }
User {
    id: 1,
    name: "Alice",
    admin: true,
}
[src/main.rs:14] Role::Admin = Admin
Admin
Tip
`Debug` and `Display` are different. `Debug` (via `{:?}`) is for developers and shows internal structure. `Display` (via `{}`) is for end users and must be implemented manually — there is no `#[derive(Display)]` in the standard library.
Clone and Copy

Clone provides a .clone() method that creates a deep copy of the value. Copy marks types that can be duplicated by simply copying their bytes — no heap allocation, no custom logic. Copy requires Clone and can only be derived if all fields are also Copy.

RUST
#[derive(Debug, Clone)]        // Clone only — String is not Copy
struct Config {
    host: String,
    port: u16,
}

#[derive(Debug, Clone, Copy)]  // Copy + Clone — all fields are Copy
struct Point { x: f32, y: f32 }

fn main() {
    // Clone: explicit deep copy
    let cfg1 = Config { host: String::from("localhost"), port: 8080 };
    let cfg2 = cfg1.clone(); // cfg1 is still valid
    println!("{:?}", cfg1);
    println!("{:?}", cfg2);

    // Copy: implicit bitwise copy — no .clone() needed
    let p1 = Point { x: 1.0, y: 2.0 };
    let p2 = p1;   // copied, not moved
    println!("{:?} {:?}", p1, p2); // p1 still usable
}
Note
Types containing a `String`, `Vec`, `Box`, or any heap-allocated data cannot be `Copy` because copying them would leave two owners of the same allocation. Use `Clone` for those types and call `.clone()` explicitly.
PartialEq and Eq

PartialEq enables the == and != operators. Eq is a marker trait that says equality is a full equivalence relation (reflexive, symmetric, transitive — and every value equals itself). Derive both together unless your type contains a float, which is not Eq because NaN != NaN.

RUST
#[derive(Debug, PartialEq, Eq)]
struct UserId(u64);

#[derive(Debug, PartialEq, Eq)]
enum Status { Active, Inactive, Banned }

#[derive(Debug, PartialEq)]  // no Eq — f64 is not Eq (NaN != NaN)
struct Measurement { value: f64, unit: String }

fn main() {
    let a = UserId(42);
    let b = UserId(42);
    let c = UserId(99);

    println!("{}", a == b); // true
    println!("{}", a == c); // false
    println!("{}", a != c); // true

    assert_eq!(Status::Active, Status::Active);
    assert_ne!(Status::Active, Status::Banned);

    // PartialEq works in assertions, HashMaps, Vec::contains, etc.
    let ids = vec![UserId(1), UserId(2), UserId(3)];
    println!("{}", ids.contains(&UserId(2))); // true
}
PartialOrd and Ord

PartialOrd enables <, >, <=, >= comparisons. Ord is the total ordering version — it adds .min(), .max(), .clamp(), and sorting. Both require PartialEq; Ord also requires Eq.

Derived ordering compares fields lexicographically in the order they are declared.

RUST
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Version {
    major: u32,
    minor: u32,
    patch: u32,
}

fn main() {
    let v1 = Version { major: 1, minor: 2, patch: 3 };
    let v2 = Version { major: 1, minor: 3, patch: 0 };
    let v3 = Version { major: 2, minor: 0, patch: 0 };

    println!("{}", v1 < v2);  // true  (minor 2 < 3)
    println!("{}", v2 < v3);  // true  (major 1 < 2)

    let mut versions = vec![v3, v1, v2];
    versions.sort();
    println!("{:?}", versions);
    // [Version { major: 1, minor: 2, patch: 3 },
    //  Version { major: 1, minor: 3, patch: 0 },
    //  Version { major: 2, minor: 0, patch: 0 }]
}
Tip
Derive `Ord` on enums to get ordering for free — variants compare in declaration order. `#[derive(Ord)] enum Priority { Low, Medium, High }` means `Priority::Low < Priority::High`.
Hash

Hash allows a type to be used as a key in HashMap and HashSet. It must be consistent with PartialEq: if a == b then hash(a) == hash(b). Always derive Hash together with PartialEq and Eq to maintain this invariant.

RUST
use std::collections::{HashMap, HashSet};

#[derive(Debug, PartialEq, Eq, Hash)]
struct Language {
    name:    String,
    version: u32,
}

fn main() {
    // Use as a HashMap key
    let mut benchmarks: HashMap<Language, f64> = HashMap::new();
    benchmarks.insert(Language { name: String::from("Rust"),   version: 2021 }, 1.0);
    benchmarks.insert(Language { name: String::from("C"),      version: 17   }, 1.05);
    benchmarks.insert(Language { name: String::from("Python"), version: 3    }, 12.3);

    for (lang, factor) in &benchmarks {
        println!("{} v{}: {:.1}x slower than baseline", lang.name, lang.version, factor);
    }

    // Use as a HashSet element
    let popular: HashSet<Language> = HashSet::from([
        Language { name: String::from("Rust"),       version: 2021 },
        Language { name: String::from("TypeScript"), version: 5    },
    ]);
    println!("Contains Rust: {}", popular.contains(&Language {
        name:    String::from("Rust"),
        version: 2021,
    }));
}
Warning
If you implement `PartialEq` manually (custom equality), you must also implement `Hash` manually to keep the two consistent. Mixing a derived `Hash` with a manual `PartialEq` that changes the equality semantics will cause silent bugs in hash maps.
Default

Default provides a Default::default() constructor that returns a sensible zero value. When derived, each field's Default::default() is called. Common defaults: 0 for numbers, false for bool, "" for String, None for Option.

RUST
#[derive(Debug, Default)]
struct AppConfig {
    host:     String,   // ""
    port:     u16,      // 0
    workers:  usize,    // 0
    debug:    bool,     // false
    timeout:  Option<u64>, // None
}

fn main() {
    // Full default
    let cfg = AppConfig::default();
    println!("{:#?}", cfg);

    // Struct update syntax — override only what you need
    let production = AppConfig {
        host:    String::from("0.0.0.0"),
        port:    443,
        workers: 8,
        ..Default::default() // debug=false, timeout=None
    };
    println!("{} on port {} with {} workers", production.host, production.port, production.workers);
}
Deriving Multiple Traits at Once

You can list multiple traits in a single #[derive(...)] attribute. Order within the list does not matter, but there is a conventional ordering that most Rust projects follow.

RUST
// Conventional ordering: Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
struct Priority(u8);

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Task {
    id:       u64,
    title:    String,
    priority: Priority,
}

fn main() {
    let p1 = Priority(1);
    let p2 = Priority(5);

    println!("{:?} < {:?}: {}", p1, p2, p1 < p2); // true

    let mut tasks = vec![
        Task { id: 1, title: String::from("Write tests"),   priority: Priority(2) },
        Task { id: 2, title: String::from("Fix bug"),       priority: Priority(5) },
        Task { id: 3, title: String::from("Update docs"),   priority: Priority(1) },
    ];
    tasks.sort_by_key(|t| t.priority);
    for t in &tasks {
        println!("[P{}] {}", t.priority.0, t.title);
    }
}
Priority(1) < Priority(5): true
[P1] Update docs
[P2] Write tests
[P5] Fix bug
Debug on Enums

#[derive(Debug)] on enums shows the variant name and any associated data. This is invaluable for debugging and logging.

RUST
#[derive(Debug, PartialEq)]
enum Event {
    Click { x: i32, y: i32 },
    KeyPress(char),
    Resize { width: u32, height: u32 },
    Quit,
}

fn main() {
    let events = vec![
        Event::Click { x: 100, y: 200 },
        Event::KeyPress('q'),
        Event::Resize { width: 1920, height: 1080 },
        Event::Quit,
    ];

    for e in &events {
        println!("{:?}", e);
    }
    // Click { x: 100, y: 200 }
    // KeyPress('q')
    // Resize { width: 1920, height: 1080 }
    // Quit

    assert_eq!(events[3], Event::Quit);
}
The serde Derives

The serde crate provides two widely-used derive macros: Serialize and Deserialize. These enable converting your types to and from JSON, TOML, YAML, MessagePack, and dozens of other formats.

Unlike the standard derives, serde macros come from an external crate and require adding serde to your Cargo.toml.

RUST
// Cargo.toml:
// [dependencies]
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct User {
    id:    u32,
    name:  String,
    email: String,
    #[serde(default)]      // use Default::default() if missing in input
    admin: bool,
    #[serde(skip)]         // never serialized or deserialized
    password_hash: String,
}

fn main() {
    let user = User {
        id:            1,
        name:          String::from("Alice"),
        email:         String::from("alice@example.com"),
        admin:         false,
        password_hash: String::from("secret"),
    };

    // Serialize to JSON
    let json = serde_json::to_string_pretty(&user).unwrap();
    println!("{}", json);

    // Deserialize back
    let parsed: User = serde_json::from_str(&json).unwrap();
    println!("Parsed: {} <{}>", parsed.name, parsed.email);
}
Note
serde's attribute macros (`#[serde(default)]`, `#[serde(skip)]`, `#[serde(rename = "...")]`) give fine-grained control over serialization behaviour without abandoning derive. See the serde page for a complete reference.
When to Implement Manually Instead of Deriving

Derive generates a structural implementation — it compares or hashes each field in declaration order. Sometimes you need different semantics and must implement manually.

RUST
// Case-insensitive string wrapper
#[derive(Debug, Clone)]
struct CiString(String);

// Derived PartialEq would be case-sensitive — we need case-insensitive
impl PartialEq for CiString {
    fn eq(&self, other: &Self) -> bool {
        self.0.to_lowercase() == other.0.to_lowercase()
    }
}
impl Eq for CiString {}

// Hash must be consistent with PartialEq:
// if a == b, then hash(a) must equal hash(b)
impl std::hash::Hash for CiString {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.0.to_lowercase().hash(state);
    }
}

fn main() {
    let a = CiString(String::from("Rust"));
    let b = CiString(String::from("rust"));
    println!("{}", a == b); // true — case-insensitive

    use std::collections::HashSet;
    let mut set = HashSet::new();
    set.insert(a);
    println!("{}", set.contains(&b)); // true — same hash bucket
}

Scenario

Use derive?

Structural comparison (all fields equal)

Yes — derive PartialEq, Eq

Custom equality (e.g. case-insensitive)

No — implement manually

Lexicographic ordering by all fields

Yes — derive Ord

Custom ordering (e.g. by one field only)

No — implement manually or use sort_by_key

Standard Debug format

Yes — derive Debug

Custom Display / Debug format

No — implement fmt::Display / fmt::Debug

All fields implement the trait

Yes — derive safely

Some fields should be excluded from comparison

No — implement manually

Custom Derive Macros (Procedural Macros)

Beyond the standard derives, Rust allows library authors to write their own #[derive(...)] macros using procedural macros. Popular examples include:

  • serde: Serialize, Deserialize
  • thiserror: Error — derives std::error::Error with formatted messages
  • clap: Parser — derives a CLI argument parser
  • diesel: Queryable, Insertable — derives database ORM traits
  • proptest: Arbitrary — generates random test values

Writing your own procedural macros involves the proc-macro crate, syn, and quote. See the Procedural Macros page for a complete walkthrough.

RUST
// thiserror example: derive Error with a formatted message
use thiserror::Error;

#[derive(Debug, Error)]
enum AppError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Parse error at line {line}: {message}")]
    Parse { line: usize, message: String },

    #[error("Not found: {0}")]
    NotFound(String),
}

fn find_config(name: &str) -> Result<String, AppError> {
    if name.is_empty() {
        return Err(AppError::NotFound(String::from("config name is empty")));
    }
    Ok(format!("config:{}", name))
}

fn main() {
    match find_config("") {
        Ok(cfg) => println!("{}", cfg),
        Err(e)  => println!("Error: {}", e), // Error: Not found: config name is empty
    }
}
All Standard Derivable Traits at a Glance

Trait

Enables

Requires

Debug

{:?} and {:#?} formatting, dbg! macro

All fields: Debug

Clone

.clone() deep copy

All fields: Clone

Copy

Implicit bitwise copy, no move

All fields: Copy; also derive Clone

PartialEq

== and != operators

All fields: PartialEq

Eq

Total equality (reflexive), enables HashMap key

PartialEq; all fields: Eq

PartialOrd

<, >, <=, >= operators

PartialEq; all fields: PartialOrd

Ord

Total ordering, sort(), .min(), .max()

Eq + PartialOrd; all fields: Ord

Hash

HashMap / HashSet key

Eq; all fields: Hash

Default

Default::default() zero constructor

All fields: Default

Success
`#[derive(...)]` is one of Rust's most practical productivity features. It eliminates boilerplate implementations for Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, and Default — each generated correctly and efficiently at compile time. Derive whenever the structural default matches your intent, and implement manually only when you need custom semantics. Third-party crates extend this system with their own derive macros for serialisation, error types, CLI parsing, and much more.