Skip to content

Please document difference with derive_where #545

@nyurik

Description

@nyurik

I am new to both derive_more and derive_where, so the only thing I could do was to run AI on both and generate this. Could you add some guideline which should be used when? I believe only the authors of the crates can tell if this is accurate or not. I'm including it here for everyone's reference (and to save some dup AI re-run energy :) ), but I have no clue if this is good.


Executive Summary

Both derive_more and derive_where are Rust proc‐macro crates that reduce boilerplate for #[derive]. derive_more (MIT license, owner JelteF) provides many custom derive macros for standard traits (e.g. Add, From, Display, Error, etc.) and associated helper methods (e.g. Constructor). It’s widely used (millions of downloads) and actively maintained (last release v2.1.1 on Dec 22, 2025【71†L534-L540】【44†L220-L222】). derive_where (MIT/Apache-2.0, owners ModProg and daxpedda) offers a single attribute #[derive_where(...)] that mimics #[derive] but without automatically inserting generic bounds. This lets you derive traits (Clone, Debug, Default, PartialEq, Eq, Ord, Hash, optionally Serde Serialize/Deserialize, etc.) for types with generics without requiring those generics to implement the traits【57†L98-L101】【24†L332-L340】. Its latest stable version is 1.6.1 (released Mar 13, 2026【50†L201-L204】). In practice, use derive_more when you need to auto-implement a variety of traits on newtypes or enums (e.g. numeric ops, conversions, error formatting), and use derive_where when you need more flexible generic trait bounds on derives. The sections below compare their traits/macros, APIs, compatibility, performance and community metrics in detail.

Crate Overviews

  • derive_more: “Some more derive(Trait) options” – provides #[derive(...)] macros for many common traits (conversions, formatting, operators, error handling, etc.) and static helpers like Constructor. Maintained by JelteF, latest version 2.1.1 (Dec 22, 2025)【54†L4-L10】【71†L534-L540】, license MIT【54†L4-L10】, repo github.com/JelteF/derive_more【54†L11-L15】. Last commit (v2.1.1) was Dec 22, 2025【71†L534-L540】【44†L220-L222】. It requires Rust 1.81+ and supports no_std (via a default “std” feature that can be disabled)【54†L81-L88】【41†L601-L608】.

  • derive_where: “Attribute proc-macro to simplify deriving standard and other traits with custom generic type bounds”【57†L98-L101】. Maintained by ModProg (owner ModProg, also daxpedda), latest stable 1.6.1 (released Mar 13, 2026)【50†L201-L204】, license MIT OR Apache-2.0【57†L9-L13】, repo github.com/ModProg/derive-where【57†L11-L13】. The last tagged release 1.6.1 was Mar 13, 2026【50†L201-L204】. It supports deriving Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash out-of-the-box, plus Serialize/Deserialize via feature flags, and Zeroize (with optional “zeroize” feature)【24†L332-L341】. It works in no_std by default【24†L364-L370】 and requires Rust 1.57+ (MSRV 1.57)【24†L385-L393】. It is essentially a single attribute macro with options for skipping fields, specifying bounds, enum defaults, etc.

Both crates are actively maintained (recent releases, CI, etc). derive_more has 2.1k★ stars, 146 forks【71†L534-L540】 and is transitively depended on by ~218k crates【71†L560-L563】, indicating broad usage. derive_where has more modest adoption (89★, 7 forks【73†L179-L180】) and ~7.3k dependent crates (search results), but it solves a narrower problem.

Derive Macros & Features Comparison

The table below summarizes each crate’s key derive macros/attributes, their behavior, supported item types (structs/enums), and a typical code example. Where derive_more groups similar derives, we list representative macros.

Macro / Feature Behavior / Implementations Supported Items Example Usage
derive_more::From Implements From<Inner> for tuple structs or enum variants, enabling type conversion. Structs, Enums (newtypes) #[derive(From)] struct MyInt(i32); // allows MyInt::from(5)【43†L311-L319】
derive_more::Into Implements Into<Inner> for types (complement of From). Structs, Enums #[derive(Into)] struct Pnt { x: i32, y: i32 } // (x,y).into() gives (x,y)
derive_more::TryFrom / TryInto Fallible conversion (returns Result), e.g. via #[derive(TryFrom)]. Structs, Enums #[derive(TryFrom)] enum N { #[try_from(i)] I(i32), U(u32) }
derive_more::FromStr Implements std::str::FromStr (parsing) based on enum variant names or pattern. Structs (no fields), Enums #[derive(FromStr)] enum Color { Red, Blue }
derive_more::IntoIterator For tuple structs, implements IntoIterator by delegating to inner. Tuple Structs #[derive(IntoIterator)] struct Wrapper(Vec<i32>); for x in Wrapper(v) { ... }
derive_more::AsRef / AsMut Derives as_ref()/as_mut() to inner type for newtype structs. Tuple Structs #[derive(AsRef, AsMut)] struct W(String); let s: &String = W("hi".into()).as_ref();
derive_more::Debug Implements Debug with optional customization (new in v1.0.0)【39†L691-L700】. Structs, Enums #[derive(Debug)] struct S(i32);
derive_more::Display Implements Display (string formatting) with optional #[display("...")] syntax. Structs, Enums (with #[display] attributes) #[derive(Display)] #[display("{_0:?}")] struct S(i32);
derive_more (fmt‑like) Also derives Binary, LowerHex, etc. (same Display macro handles these). Structs, Enums #[derive(Display)] #[display("{:x}", _0)] struct Hex(u32);
derive_more::Error Implements std::error::Error and optionally Display::Error sources. Enums (error types) #[derive(Error, Display)] #[display("{msg}")] struct MyErr { msg: String }
derive_more::Index / IndexMut Implements Index<Idx> for newtypes wrapping collections. Structs (tuple with one field) #[derive(Index)] struct D(Vec<i32>); let x = D(vec![1,2])[0];
derive_more::Deref / DerefMut Implements Deref to inner type for wrapper newtypes. Structs (tuple or single-field struct) #[derive(Deref, DerefMut)] struct Wrap(String);
derive_more::Not / Neg Implements ! or unary - operators for numeric newtypes. Structs (tuple of one numeric type) #[derive(Not, Neg)] struct Flag(bool);
derive_more (Add-like) Implements Add, Sub, BitAnd, BitOr, BitXor (with _assign variants) for newtypes. Structs (tuple of numeric types) #[derive(Add, Sub)] struct MyInt(i32);
derive_more (Mul-like) Implements Mul, Div, Rem, Shl, Shr (with _assign variants). Structs (tuple of numeric types) #[derive(Mul, DivAssign)] struct Factor(f64);
derive_more::Sum / Product Implements Sum or Product for iterator-based accumulation. Structs (tuple of numeric) #[derive(Sum)] struct Tot(i64); let x: Tot = vec![Tot(1), Tot(2)].into_iter().sum();
derive_more::Eq / PartialEq Derives equality (with generic handling as of v2.1.0). Structs, Enums #[derive(PartialEq, Eq)] struct X(T); – new in 2.1.0, treats generics structurally【41†L580-L588】.
derive_more::Constructor Adds a fn new(field1, field2) -> Self constructor for structs. Structs (tuple or named fields) #[derive(Constructor)] struct P(x: i32, y: i32); // impl P::new(x,y)
derive_more::IsVariant For each enum variant, derives an is_variant() method. Enums #[derive(IsVariant)] enum E { A, B } // impl E::is_a(&self) -> bool, etc.
derive_more::Unwrap / TryUnwrap For enums, derives unwrap_variant() and fallible versions. Enums #[derive(Unwrap)] enum E { A(i32), B } // impl E::unwrap_a()
derive_where (attribute) Like #[derive(...)] but omits requiring generic trait bounds: generates impls for any type parameters. Traits supported include Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash plus optionally Serde Serialize/Deserialize and Zeroize (with features)【24†L332-L341】. It also supports skipping fields/variants for certain traits. Structs, Tuple structs, Unions, Enums (except only-unit cases)【24†L350-L358】 #[derive_where(Clone, Debug)] struct S<T>(T); // impl Clone for all T【56†L108-L113】. Optionally: #[derive_where(Clone, Debug; T: Clone)] to bind T: Clone【56†L132-L140】.

Notes: The derive_more macros must be enabled via Cargo features (e.g. "derive_more = { version = "2", features = ["add", "from", ...] }"【43†L476-L484】) to avoid long compile times. By default it only provides macros (not the trait imports) unless you use the with_trait module. In contrast, derive_where is a single proc-macro attribute; you write #[derive_where(...)] much like #[derive], with optional arguments after a semicolon to specify bounds【56†L130-L138】【56†L168-L171】. derive_where also offers field- or variant-level options #[derive_where(skip)], #[derive_where(skip_inner)], and #[derive_where(incomparable)] to exclude fields from generated impls【56†L184-L193】【24†L371-L379】.

The key overlap is that both can derive Debug, Clone, Eq/PartialEq, etc., but they do so differently: derive_more implements the trait only when you specify it (with any needed generic bounds), whereas derive_where always omits generic bounds (so #[derive_where(Clone)] never requires T: Clone, whereas #[derive(Clone)] would). Many derive_more traits have no direct analogue in derive_where (e.g. arithmetic, conversion traits, Display/Error, static methods). Conversely, derive_where’s special features (custom bounds, skip behavior) have no match in derive_more.

API & Ergonomics

  • Import/Usage: derive_more macros are imported like other custom derives: e.g. use derive_more::{Add, From, Display}【54†L98-L100】. You must enable each group as a feature. If you only need a few traits, compile-time is reduced by enabling fewer features. By default no derives are supported unless features are turned on【43†L453-L462】. In no_std contexts you disable the default std feature【43†L466-L474】.
    derive_where is an attribute on a type: #[derive_where(...)] struct S<T>(T);【56†L108-L113】. You only add derive-where = "1" to Cargo.toml (it has no feature flags besides optional serde/zeroize). Multiple #[derive_where] attributes can stack (e.g. #[derive_where(Clone)] #[derive_where(Debug)])【56†L115-L123】, and you can qualify the crate name if renamed. Overall its API is simpler (one macro with options) but less granular.

  • Trait Bounds Handling: This is the main design difference. derive_where by default generates impls without any T: Trait bounds, whereas normal Rust derive and derive_more’s derive macros only derive impls when the generics satisfy the trait. For example, #[derive_where(Clone)] struct S<T>(T); implements Clone for all T, by effectively calling Default::default() for skipped fields or ignoring them. You can add custom bounds after a semicolon: #[derive_where(Clone; T: Clone)] will bind T: Clone【56†L132-L140】. derive_more’s derives generally infer bounds on the struct’s type parameters automatically (as of v2.1.0, they do so “structurally” for operator traits)【41†L603-L609】. derive_more also allows specifying custom error types for some derives (FromStr, TryInto can specify error)【41†L593-L601】.

  • Compile-time: derive_more can become heavy if many features are enabled. Its documentation explicitly advises enabling only needed derive types to speed up compilation【43†L453-L462】. derive_where is relatively lightweight (one attribute macro), and you pay cost only for the traits you list. In both cases, using tools like cargo expand can reveal generated code【22†L346-L354】. The generated code patterns differ: derive_more’s macros often forward to the wrapped field (e.g. impl Add for S(T) does { S(self.0.add(rhs.0)) }), whereas derive_where’s code usually calls Self::default() or ignores fields when generics are not bounded. Error messages can differ: derive_more’s macro errors generally point to missing type implementations or misuse of attributes; derive_where errors often occur if you misuse skip options or specify impossible bounds. No major complaints appear in issue trackers about poor error clarity.

  • Ergonomics: derive_more’s usage can be more verbose due to multiple derives and feature flags. It has helpful auto-completion for many traits. derive_where’s single attribute is convenient for cases where many traits need no-bounds derives. derive_where’s options for skipping fields/groups (e.g. skip, skip_inner, named skip groups) provide fine-grained control that derive_more does not. For enum defaults, derive_where adds support for #[derive_where(default)] on a variant (enabled by Rust 1.62)【57†L172-L180】, something derive_more doesn’t address.

Compatibility & Ecosystem

Both crates support modern Rust editions (2018/2021/2024) and are on crates.io. derive_more targets MSRV 1.81 (per docs)【54†L50-L54】; derive_where supports MSRV 1.57 (CI checks)【24†L385-L393】. derive_more’s macros generally work in no_std (if you disable the default std feature)【43†L466-L474】, and derive_where is no_std by default【24†L364-L370】.

  • Interactions with Other Crates:
    • serde: derive_where explicitly supports Serde via an optional serde feature to derive Serialize/Deserialize without bounds【24†L338-L345】. derive_more does not provide serde derives (you’d use serde::Serialize), but it coexists fine; you can use both on a struct if needed (#[derive(Serialize)] #[derive_more::Display] etc).
    • thiserror/anyhow: derive_more’s improved Display and Error aims to replace thiserror for error types【39†L699-L708】. For example, #[derive(Display, Error)] can format error messages with attributes similarly to thiserror, and uses a real Error type for sources (instead of a &'static str)【39†L699-L708】. derive_where has no notion of Error/Display on enums (it’s trait-agnostic).
    • Other crates: derive_more re-exports many traits so it doesn’t conflict with trait imports【43†L419-L428】. derive_where is trait-neutral and simply generates impls; it can be used alongside any other derives (including those from serde, derivative, educe, etc). Some users combine them: e.g. #[derive_more(Add)] #[derive_where(Clone)] is valid. There are no known compatibility issues reported.

Performance & Benchmarks

We found no published benchmarks directly comparing these macros. Anecdotally, enabling only needed derive_more features mitigates compile slowdown. derive_where’s GitHub README claims it avoids using unsafe code unless a safe feature is disabled【24†L369-L378】. Users have not reported significant performance issues unique to either crate. (General guidance: procedural macros do add compile time, but both are reasonably optimized.)

Community & Maintenance

Metric derive_more derive_where
Stars (GitHub) 2.1k【71†L534-L540】 89【73†L179-L180】
Forks 146【71†L542-L544】 7【73†L179-L180】
Issues Open 70【70†L12-L15】 12【73†L185-L187】
PRs Open 12【70†L12-L15】 1【73†L185-L187】
Latest Release 2.1.1 (Dec 22, 2025)【71†L534-L540】 1.6.1 (Mar 13, 2026)【50†L201-L204】
Downloads (crates.io) ~hundreds of millions (all-time) ~38 million (all-time)
Used by (GitHub) ~218k repos【71†L560-L563】 ~7.3k (as noted on GitHub)
Notable issues/PRs Many minor fixes and features (custom error support, skip fields, etc)【41†L593-L601】【39†L612-L619】 Active development on skip/group fixes (e.g. “Clone skip group and Zeroize variant skip fix” v1.4.0)【69†L239-L244】; some closed PRs on enum support.

derive_more is very popular and actively maintained (releases in late 2025 for v2.x). Its issue tracker shows active community contributions (bug fixes, feature requests like better generics support【41†L603-L610】). derive_where has less traffic but also regular releases (v1.6.0 in Aug 2025, v1.6.1 Mar 2026) and a focus on correctness (e.g. verifying Rust’s enum discriminants). Issue volume is low (≈12 open) and appears manageable.

Migration & Usage Guidance

When to use which crate:

  • Use derive_where if you need to derive standard traits without forcing generic bounds. For example, to implement Clone/Debug/Eq on a generic wrapper and not require T: Clone, write:

    #[derive_where(Clone, Debug, Eq, PartialEq)]
    struct Wrapper<T>(T);

    This generates impl<T> Clone for Wrapper<T> (no T: Clone bound)【56†L108-L113】. By contrast, #[derive(Clone)] alone would require T: Clone. If you later need a specific bound, you can add it after a semicolon: #[derive_where(Clone; T: Clone)].

  • Use derive_more when you need other traits (e.g. mathematical ops, conversions, Display, etc.) on simple structs/enums. For example:

    use derive_more::{From, Add, Display};
    #[derive(PartialEq, From, Add, Display)]
    struct MyInt(i32);

    This makes MyInt::from(i32), Add (so you can add MyInt), and Display::to_string() work as expected【22†L311-L320】. derive_more cannot derive Clone for a generic type without bounds, so it complements derive_where in many cases.

Often both can coexist: e.g. on a struct with generics, you might write: #[derive_more(Add, From)] #[derive_where(Clone, Debug)]. That applies trait derives from derive_more and a separate derive_where attribute. Just be careful to import the macros (use derive_more::{...}; use derive_where::derive_where;).

Example (equivalent impls): Consider an enum with a generic:

enum E<T> { A(T), B }
// With derive_more:
#[derive(PartialEq, Eq)]             // requires T: PartialEq/Eq
enum E1<T: PartialEq>(T, T);
// With derive_where:
#[derive_where(PartialEq, Eq)]     // no T bound by default
enum E2<T>(T, T);

Here E1<T> needs T: PartialEq + Eq, whereas E2<T> (derive_where) works for any T. If desired, you can have both: #[derive_where(PartialEq; T)] #[derive_more(PartialEq)] to emulate the standard derive.

Pitfalls: derive_more’s wide trait list means it can inflate your API surface; only enable needed features to avoid long build times. derive_where’s free-for-all derivation can be surprising: it might implement Eq even when it semantically shouldn’t, so use skip if you want to exempt certain fields from equality【56†L184-L193】. Also, derive_where does not support union derives except Clone/Copy【24†L352-L358】.

Security, Licensing, Stability

  • Security: Both crates are mature and widely used; no known security vulnerabilities are reported. derive_more’s code is essentially just delegating to safe trait impls, and derive_where’s default path is safe (it avoids unsafe unless a “nightly” feature is enabled)【24†L369-L378】. Always review auto-generated impls if security-sensitive (e.g. Zeroize usage in derive_where).
  • Licensing: derive_more is MIT (per [54†L4-L10]), whereas derive_where is dual-licensed MIT/Apache-2.0【57†L9-L13】. Both are permissive and compatible with commercial use.
  • API Stability: derive_more v2 adheres to semver; however, its changelog shows occasional breaking changes in major releases (e.g. v2.0.0 had breaking changes to import style)【39†L719-L727】. Most users will stick to a specific minor version if MSRV is a concern【54†L81-L90】. derive_where increments minor versions when MSRV changes【24†L385-L393】. Both crates document breaking changes (see their CHANGELOGs).

Decision Flowchart

flowchart TB
    A[Need automatic derives for types?] --> B{Require custom generic bounds?}
    B -->|Yes| C[Use derive_where for traits like Clone, Debug, Eq]
    C --> D{Also need other traits?}
    D -->|Yes| E[Combine with derive_more for ops/conversions]
    D -->|No| F[derive_where alone suffices]
    B -->|No| G{Need many auto-derived traits?}
    G -->|Yes| H[Use derive_more (enable needed features)]
    G -->|No| I[Use standard #[derive] or other crate]
    H --> J{Need to avoid generic bounds?}
    J -->|Yes| E
    J -->|No| H
Loading

This chart summarizes typical choices: if you only need to sidestep generic bounds on standard traits, go with derive_where (and you can optionally still use derive_more for others). If you want a broad set of auto-derives (e.g. arithmetic, conversions, Display, Error), use derive_more. In some cases, both will be applied to cover all needs.

Sources: Official crate docs and READMEs were used for descriptions and feature lists【54†L98-L100】【57†L98-L101】【24†L332-L341】【22†L311-L320】. Version, license, and repository info come from docs.rs and GitHub【57†L9-L13】【54†L4-L10】【50†L201-L204】【71†L534-L540】. Community metrics are from GitHub repository pages【71†L534-L540】【73†L179-L180】【69†L165-L173】 and download stats from crates.io. Relevant discussions and change logs provided details on API changes【39†L719-L727】【41†L603-L609】.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions