num-derive

Procedural derive macros that implement num-traits' numeric traits — FromPrimitive, ToPrimitive, Zero, One, and more — on your own Rust enums and structs.

Library
Cargo
v0.5.1
225stars
MIT OR Apache-2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
61/100Good
Development Activity56
Maintenance32
Community76
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
63/100Good
Architecture78
Code Quality74
Innovation40
Learning Curve60

num-derive is a companion crate to num-traits that supplies #[derive(...)] macros for the numeric traits Rust’s standard library and the num ecosystem expect. Instead of hand-writing impl FromPrimitive for MyEnum or impl Zero for MyNewtype boilerplate, you annotate the type and the macro generates a compile-time-checked implementation.

It covers ten traits — FromPrimitive, ToPrimitive, NumOps, NumCast, Zero, One, Num, Float, Signed, and Unsigned — and understands two common shapes: C-like enums (deriving From/ToPrimitive based on discriminant values) and single-field newtype structs (auto-delegating the trait to the wrapped inner numeric type). It has no runtime footprint since proc-macro crates compile away entirely, and it stays deliberately narrow in scope, doing one job for the num-traits ecosystem rather than trying to be a general derive-macro toolkit.

What You Get

  • Derive macros for ten numeric traits: FromPrimitive, ToPrimitive, NumOps, NumCast, Zero, One, Num, Float, Signed, and Unsigned
  • Automatic discriminant-based FromPrimitive/ToPrimitive generation for C-like enums
  • Newtype delegation — deriving a trait on a single-field tuple struct forwards the implementation to the wrapped inner type
  • A #[num_traits = "..."] attribute to point the macros at a re-exported or renamed num_traits crate when it’s only a transitive dependency
  • no_std compatibility (verified in CI against a bare-metal thumbv6m target) so it works in embedded contexts
  • Compile-time diagnostics — malformed input produces a proper compile_error! rather than a panic during your build

Common Use Cases

  • Converting between a C-like Rust enum and its underlying integer representation (e.g. FFI enums, protocol opcodes, state machine variants) via FromPrimitive/ToPrimitive
  • Making a numeric newtype wrapper (e.g. struct Meters(f64)) participate in generic numeric code that’s bounded by num_traits::Num or Zero/One
  • Implementing embedded/no_std numeric types that still need to satisfy the standard num-traits trait bounds used by generic math code
  • Reducing boilerplate in codebases that define many small numeric wrapper types (currency amounts, IDs, physical units) needing consistent trait coverage

Under The Hood

Architecture num-derive is a single-file proc-macro crate (src/lib.rs, ~1000 lines) exposing ten #[proc_macro_derive(...)] entry points — FromPrimitive, ToPrimitive, NumOps, NumCast, Zero, One, Num, Float, Signed, and Unsigned. Each follows the same template: parse the input via syn into a Data::Enum or Data::Struct, branch on newtype-vs-enum shape using the shared newtype_inner helper, build the implementation with quote!, and return a TokenStream. A shared NumTraits resolver and anon_const_trick helper (a pattern borrowed from serde) wrap generated code in a hygienic anonymous const _: () = { ... } block so an extern crate num_traits as _num_traits alias can be injected regardless of where the derive is used, letting num_traits be a direct or transitive dependency. Because every derive macro depends on this shared import-resolution core, a change to it would affect all ten macros simultaneously; there’s no other coupling between them.

Tech Stack Built on Rust’s standard proc-macro trio — proc-macro2 for token-stream manipulation, syn v3 for parsing Rust syntax into an AST, and quote for code-generation templating. Dev-dependencies num 0.4 and num-traits 0.2 exist only to exercise the derived implementations in the test suite; the published crate has zero runtime dependencies since proc-macro crates compile away at build time. CI (GitHub Actions) runs the test matrix against rustc 1.71 (MSRV), stable, beta, and nightly, plus a dedicated no_std check against the thumbv6m-none-eabi embedded target via a separate crate under ci/, and enforces cargo fmt --check.

Code Quality The test suite (tests/) is organized around behavior rather than mirroring source structure: trivial.rs and newtype.rs cover the two core code paths, with edition-specific [[test]] entries (2015/2018) verifying macro hygiene doesn’t regress across Rust editions, and issue-6.rs/issue-9.rs/issue-16.rs are regression tests tied to specific historical bug reports — a healthy pattern for a macro crate where subtle hygiene bugs recur. Parse errors are converted to compile_error! diagnostics via a small parse! macro rather than panicking; the one exception is newtype_inner, which deliberately panic!()s on an unsupported named-field newtype shape (an explicit unimplemented-feature guard, not a swallowed error). No unsafe code is present. rustfmt is CI-enforced; no clippy step is visible in the reviewed workflow.

What Makes It Unique num-derive doesn’t introduce new numeric semantics — it automates already-established num-traits interfaces. Its specific contributions are the newtype auto-delegation (deriving numeric traits on a single-field wrapper by forwarding to the inner type) and the anonymous-const import-hygiene trick that lets the macros work whether num_traits is a direct or renamed/transitive dependency. Solid, focused engineering applied to a well-understood boilerplate problem rather than a novel technical approach.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search