When boilerplate steals your focus
You are building a wrapper around a third-party type. Maybe a Config struct that holds a HashMap and some metadata. You want to treat your Config like a HashMap so you can call .get() and .insert() without typing self.0 or writing a dozen forwarding methods. You start writing impl Deref for Config, then impl DerefMut, then impl From<Config> for HashMap, then impl Display. Your editor fills with repetitive code that does nothing but delegate. You realize you are spending more time writing glue than logic.
The derive_more crate exists to kill that boilerplate. It provides derive macros that generate standard trait implementations based on your struct or enum layout. You add #[derive(Deref, From, Display)] to your type, and the macro writes the impl blocks for you. It handles the boring parts: dereferencing to a field, converting from a variant, formatting fields, implementing Error chains. You keep the structure. The macro fills in the behavior.
Stop writing delegation by hand. Let the compiler generate it.
What derive_more actually does
derive_more is a procedural macro crate. When you apply a derive attribute, the macro runs during compilation, inspects your type definition, and emits Rust code. It does not guess. It follows strict rules based on your type layout. For a tuple struct with one field, it assumes that field is the target. For a named struct with multiple fields, it requires you to mark the target with an attribute like #[deref].
Think of it like a specialized stamp press. You feed it your type definition. It reads the shape, applies the correct trait template, and prints the exact impl block you would have written. The difference is speed and consistency. You avoid typos in trait signatures. You avoid forgetting to forward a method. You keep your source file focused on domain logic instead of plumbing.
The crate is modular. You enable only the traits you need via feature flags in Cargo.toml. This keeps compile times low and avoids pulling in unused dependencies. The community convention is to list features explicitly. A blanket derive_more = "2" works but drags in everything, which slows down your build. Be specific.
[dependencies]
# Enable only the features you use. This reduces compile time.
derive_more = { version = "2", features = ["deref", "from", "display", "error"] }
The macro writes the code you would write anyway. You just save the keystrokes.
Minimal example
Start with a newtype wrapper. A newtype is a tuple struct with a single field. It is the most common use case for derive_more.
Here is the smallest case: a wrapper around a string that carries semantic meaning.
use derive_more::{Deref, From, Display};
/// A wrapper around a String that carries semantic meaning.
#[derive(Debug, Deref, From, Display)]
pub struct UserId(String);
fn main() {
// From<String> allows this conversion via the From trait.
let id = UserId::from("user-123");
// Deref<String> allows calling String methods directly.
assert_eq!(id.len(), 9);
// Display formats the inner value.
println!("ID: {}", id);
}
Run this. See the output. The macro did the work.
How the macro expands
When you write #[derive(Deref)] on UserId(String), the macro generates an implementation of the Deref trait. The generated code looks like this:
impl std::ops::Deref for UserId {
type Target = String;
fn deref(&self) -> &String {
&self.0
}
}
For From, it generates:
impl From<String> for UserId {
fn from(value: String) -> Self {
UserId(value)
}
}
For Display, it generates a fmt implementation that writes the inner string. The macro handles the boilerplate of matching the trait signature and delegating to the field. You can verify the expansion by running cargo expand in your terminal. This is a valuable debugging tool. If the macro behaves unexpectedly, cargo expand shows exactly what code was emitted.
Convention aside: most developers run cargo expand once when learning a new macro, then trust it. The macro authors test edge cases extensively. You only need to peek under the hood when the compiler rejects your code and the error message points to macro expansion.
Trust the expansion. If you are curious, run cargo expand and read the generated code.
Realistic usage: wrappers and errors
Real code often involves enums and multiple fields. derive_more handles these with attributes. Consider a cache wrapper and an error enum.
Here is a practical setup: a cache that delegates to a HashMap, plus an error type that formats itself and converts from io::Error.
use std::collections::HashMap;
use std::io;
use derive_more::{Deref, DerefMut, From, Display, Error};
/// A cache that wraps a HashMap but exposes map-like behavior.
#[derive(Debug, Deref, DerefMut)]
pub struct Cache {
// Mark the field to dereference.
#[deref]
#[deref_mut]
data: HashMap<String, Vec<u8>>,
}
/// Errors that can occur in the cache.
#[derive(Debug, From, Display, Error)]
pub enum CacheError {
// From<io::Error> converts io::Error into CacheError::Io.
#[display(fmt = "IO error: {}", _0)]
Io(io::Error),
// From<String> converts String into CacheError::NotFound.
#[display(fmt = "Key not found: {}", _0)]
NotFound(String),
}
fn main() {
let mut cache = Cache { data: HashMap::new() };
// DerefMut lets us use HashMap methods directly.
cache.insert("key".to_string(), vec![1, 2, 3]);
// From allows easy error conversion.
let err: CacheError = io::Error::other("disk full").into();
println!("Error: {}", err);
}
The #[deref] attribute tells the macro which field to target when the struct has multiple fields. Without it, the macro emits an error because it cannot guess. The #[display(fmt = "...")] attribute defines the formatting string. _0 refers to the first field of the variant. For named fields, you can use the field name directly.
Convention aside: always place #[deref] and #[deref_mut] on the same field unless you deliberately want asymmetric read and write targets. Mixing them across fields confuses readers and breaks the mental model of a transparent wrapper.
Wrap the complexity. Expose the simplicity. That is the newtype pattern.
Advanced traits: Display, TryFrom, and more
derive_more covers more than the basics. The Display derive supports rich formatting. You can reference fields by name or position.
Here is a struct that formats itself with custom syntax:
use derive_more::Display;
#[derive(Display)]
pub struct User {
#[display(fmt = "User({name}, {age})")]
name: String,
age: u8,
}
When conversion can fail, use TryFrom. The macro generates a Result wrapper automatically.
use derive_more::TryFrom;
#[derive(TryFrom)]
pub struct Positive(i32);
This generates impl TryFrom<i32> for Positive that returns Err for negative values if you add validation logic, or simply wraps the value. For enums, TryFrom can convert from a variant's inner type.
Sometimes you just want a constructor function. #[derive(Constructor)] adds a new() function.
use derive_more::Constructor;
#[derive(Constructor)]
pub struct Point {
x: f64,
y: f64,
}
// Generates: impl Point { pub fn new(x: f64, y: f64) -> Self { ... } }
For enums, #[derive(IsVariant)] adds boolean check methods.
use derive_more::IsVariant;
#[derive(IsVariant)]
pub enum Status {
Active,
Inactive,
}
// Generates: impl Status { pub fn is_active(&self) -> bool { ... } }
These traits reduce the need for manual impl blocks across your codebase.
Pitfalls and compiler errors
If you forget to enable a feature in Cargo.toml, the macro will not exist. You will get E0433 (failed to resolve: could not find Deref in derive_more). The fix is always the feature flag. Check your dependencies first.
If you have a struct with multiple fields and do not mark #[deref], the macro emits an error. The message will say something like "Cannot derive Deref for struct with multiple fields without #[deref] attribute." Add the attribute to the target field.
From derives can be tricky for structs with multiple fields. derive_more's From usually works for newtypes or enums. For a struct with multiple fields, From might not derive automatically for the inner types unless you specify which field to convert from. If you need complex conversion logic, write the impl manually.
Display formatting syntax can be strict. If you reference a field that does not exist, the macro emits an error. Ensure field names match. For tuple variants, use _0, _1. For named fields, use the name.
derive_more does not handle serialization. Use serde for Serialize and Deserialize. derive_more focuses on traits like Deref, From, Display, and Error.
Check your features first. Half of derive_more issues are missing flags, not broken code.
When to use derive_more
Use derive_more for newtype wrappers where you want the wrapper to act exactly like the inner type. Use derive_more for enums where you need From, TryFrom, or Display based on variants. Reach for manual implementations when the conversion or formatting requires logic beyond field delegation. Use thiserror for complex error types with source chains and custom attributes; derive_more covers the basics but thiserror is the ecosystem standard for errors. Pick serde for serialization; derive_more focuses on traits like Deref, From, and Display, not data interchange.
Automate the pattern. Write the exception.