reflect: a compile-time reflection model for Rust derive macros
Compile-time reflection API for developing robust procedural macros (proof of concept)
At a glance
- What is it?
- The reflect crate explores what it would look like to write procedural macros against something resembling runtime reflection, with the library doing the type level work that macro authors otherwise do by hand. It is Apache-2.0 or MIT licensed and labelled a proof of concept, so it is a design document you can compile rather than a dependency to ship.
- Who is it for?
- Adopt reflect if you are writing procedural macros today and want to understand the compile-time reflection model before it becomes mainstream, or if you want a working reference implementation of the idea. Do not adopt it as a production dependency: the crate describes itself as a proof of concept, its version is 0.0.16, and the API is explicitly experimental.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 96 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A memorial for the idea Rust lacks
The repository opens with a question rather than a description. The title is I thought Rust doesn't have reflection...?
The crate description in the manifest is blunter still: the but I thought Rust doesn't have reflection memorial brand new way of defining procedural macros. So this is not a reflection library in the sense of a runtime facility. It is an exploration of one specific question, which the README puts as tackling the 80% use case of custom derive macros through a programming model that resembles compile-time reflection.
The motivation section explains why. The author's own syn and quote libraries approach procedural macros in a super general way and are a good fit for maybe 95% of use cases, but the generality comes with the cost of operating at a relatively low level of abstraction. The macro author is responsible for the placement of every single angle bracket, lifetime, type parameter, trait bound and phantom data. There is a large amount of domain knowledge involved and very few people can reliably produce hard macros with this approach.
The design goal that follows is stated in the strongest terms available: make all the edge cases disappear, such that if your macro works for the most basic case, then it also works in every tricky case under the sun.
That framing explains the licensing choice and the version number. The manifest declares the licence as MIT OR Apache-2.0, with both LICENSE-APACHE and LICENSE-MIT at the root, and the version is 0.0.16 with the category development-tools::procedural-macro-helpers. A zero major version with a patch-level number is the convention for something whose API may change, and the description calling it a proof of concept says the same thing.
A reflection API that never runs at runtime
The programming model section is the heart of the project, and it is worth being precise about what the library does because it is easy to misread.
The idea is that the library exposes what looks like a boring straightforward runtime reflection API, the kind you would recognise from reflection in Java or reflection in Go. The macro author writes their logic in terms of that API, using types like reflect::Value to retrieve function arguments, access fields of data structures and invoke functions. Importantly, the README says, there is no such thing as a generic type or phantom data in this model. Everything is just a reflect::Value with a type that is conceptually its monomorphized type at runtime.
Meanwhile the library tracks the control flow and function invocations to build up a fully general procedural implementation of the author's macro. The resulting code will have all the angle brackets and lifetimes and bounds and phantom types in the right places without the macro author thinking about any of that.
Then the important clarification, which the README states as a matter of fact: the reflection API is just a means for defining a procedural macro. The library boils it all away and emits clean Rust source code free of any actual runtime reflection. And it is explicit that this is not a statement about compiler optimizations, that the project is not relying on the Rust compiler to do heroic optimizations on bad generated code. Literally the source code authored through the reflection API will be what a seasoned macro author would have produced simply using syn and quote.
From the perspective of the person who ends up calling the macro, the README says everything about how it is called is the same as if the macro were written the old fashioned way, and their code compiles exactly as fast and performs exactly as fast. The advantage is entirely to the macro author.
That is a well scoped claim, and it is the claim to evaluate the project against.
Declaring the runtime surface with a library macro
The implementation starts with a DSL declaration of the types and functions that will be required at runtime. The example in the README declares the pieces of the standard library formatting machinery that a Debug derive needs:
type Result;
type DebugStruct;
trait Debug {
fn fmt(&self, &mut Formatter) -> Result;
}The full declaration is larger and includes a Formatter type, a Formatter impl providing debug_struct, and a DebugStruct impl providing field and finish, all inside a mod fmt inside an extern crate std inside the reflect::library! invocation.
The mechanism is a signature declaration, not a value declaration. You are telling the library the shape of the API the generated code will call, and the library infers the types of everything statically from those signatures. The README says exactly that: throughout the rest of the macro implementation, all type information is statically inferred based on the signatures given in this library declaration.
It also anticipates the obvious extension question. There may be additional extern crate blocks if types from outside the standard library are needed, and the example given is Serde's derive macro, which would want to list the serde crate, the Serialize and Serializer types, and whichever of their methods will possibly be invoked at runtime.
This is the part of the design that makes the whole thing plausible. The generated code needs to call real functions with real generic parameters, and the library has to know those signatures to place the type arguments correctly. Declaring them once, in a readable form, is the entire trick.
The runtime side of that declaration is not free, though. The generated code carries a dependency on the exact types you declared, which is the same constraint the low level approach imposes, only moved from angle brackets scattered through the macro into one declaration block.
The derive entry point and the callback shape
The macro entry point is an ordinary proc_macro_derive function, just as it would be for a derive macro defined any other way, and the README shows the shape of it:
use proc_macro::TokenStream;
// Macro that is called when someone writes derive(MyDebug) on a data structure.
// It returns a fragment of Rust source code (TokenStream) containing an
// implementation of Debug for the input data structure. The macro uses
// compile-time reflection internally, but the generated Debug impl is exactly
// as if this macro were handwritten without reflection.
#[proc_macro_derive(MyDebug)]
pub fn derive(input: TokenStream) -> TokenStream {The interesting part is what happens after that. The tokens describing the data structure are fed into the reflection library for parsing and analysis, and a callback is provided that describes what trait implementations the library will need to generate code for. Inside the callback, ex.make_trait_impl is called with the runtime trait, the target type, and a closure that builds the function body, where block.make_function attaches the fmt implementation to the trait method.
The callback shape is what makes the library usable. The macro author does not describe the output directly, they describe the intent, and the library works out how to express it. That is why the design goal is that edge cases disappear: the callback is a small surface, and everything fiddly happens inside it.
Then there is the reassurance about what actually ships. Despite what it may look like below, everything written here executes at compile time. The reflect library spits out generated code in an output TokenStream that is compiled into the macro user's crate, and this token stream contains no vestiges of runtime reflection.
The following function in the README, the one that receives reflect::Value arguments and can pull out fields, inspect attributes and invoke methods, is the part that looks like runtime reflection. It is compiled into Debug::fmt, so it runs at compile time, in the macro author's process, and never exists in the binary the macro user ships.
The proof of concept is a working Debug derive
The demo section states plainly that this project contains a proof of concept of a compile-time reflection API for defining custom derives, and the tests/debug directory demonstrates a working compilable implementation of a Debug derive for structs with named fields.
The test case shows what code is emitted when deriving Debug for a struct called Point with two fields, and the README says it is equivalent to the code that a handwritten Debug derive macro without reflection would emit for the same data structure. That equivalence claim is the acceptance criterion, and it is the right one to check.
The repository layout backs up that this is a library with real machinery rather than a sketch. There is a src directory, a macros directory that is a separate workspace member and is published as reflect-internal, a tests directory, and a Cargo.toml with a workspace whose only member is macros. The dependencies are the standard procedural macro toolchain, proc-macro2, quote, ref-cast and syn version 3, plus the internal macros crate pinned to the exact same version with a path dependency.
The manifest also says the minimum supported Rust version is 1.71, the edition is 2021, and the docs.rs metadata pins the build to a single target, x86_64-unknown-linux-gnu, with rustdoc arguments that generate links to definitions and macro expansions and point the standard library documentation links at doc.rust-lang.org. Those rustdoc arguments matter for a crate like this, because the whole point is that a reader can follow from a reflect::Value call to the standard library function it will end up invoking.
The category recorded in the manifest, development-tools::procedural-macro-helpers, and the keyword, reflection, tell you where the crate believes it sits in the ecosystem.
What this is for, and what it is not
It is worth being clear about the boundaries, because the crate is unusual and it is easy to adopt it for the wrong reason.
It is not a runtime reflection facility. There is no reflect::TypeId, no dynamic dispatch on arbitrary types, no way to enumerate the fields of a value at runtime in your own program. The reflect::Value type is a compile-time fiction that the library uses to model the code it will generate, and it disappears from the output entirely.
It is also not a replacement for syn and quote. The author wrote those, and the README frames them as good for maybe 95% of use cases. The claim here is about the remaining five percent, the hard cases where the angle brackets and lifetimes and bounds have to be exactly right in every position, and where a macro author who is not one of the people who wrote syn will get it wrong. For that group, reflect makes the work tractable. For the other 95% it is a different way of writing the same thing, with more setup.
The real cost is the coupling. Once your derive is written through this API, the generated code depends on the library's ability to infer your types correctly across every edge case you encounter, and a bug in that inference is a bug in your macro that is hard to localise because the symptom appears in generated code you did not write. The low level approach localises failures better, at the cost of more work up front.
The versioning tells you how to plan for it. Zero major, patch level increments, a proof of concept label in the description, and the roadmap not present in this repository at all. If you adopt it, pin an exact version and expect to re-pin, because the API is explicitly experimental and the author says so.
For a comparison that keeps this in proportion, a derive macro written directly with syn and quote produces the same output and gives you full control over every token, at the price of the expertise the project is trying to make unnecessary. Reflect trades control for reach, and that is a good trade for the five percent, and a poor one for everything else.
On maintenance, the repository is not archived and the last push was on 2026-07-18, with release 0.0.16 the same day, and 0.0.15 from 2025-03-03 before it. The release history is sparse, which fits a research project rather than a widely adopted dependency, and it is a signal that this is code to read and learn from.
Editorial conclusion
Adopt reflect if you are writing procedural macros today and want to understand the compile-time reflection model before it becomes mainstream, or if you want a working reference implementation of the idea. Do not adopt it as a production dependency: the crate describes itself as a proof of concept, its version is 0.0.16, and the API is explicitly experimental. Verify first that the generated code for your own derive matches what a hand written syn and quote implementation would produce, since that equivalence is the whole claim of the project, and check that the crate's own minimum Rust version, 1.71, fits your toolchain.
Frequently asked questions
What is the reflect crate for Rust?
It explores what it could look like to write custom derive macros through a programming model that resembles compile-time reflection, handling the placement of angle brackets, lifetimes, type parameters, trait bounds and phantom data so the macro author does not have to.
Does the reflect crate add runtime reflection to Rust programs?
No. The reflection API is only a means for defining a procedural macro. The library emits clean Rust source with no actual runtime reflection, and the generated code compiles and performs exactly as if the macro had been written by hand with syn and quote.
What is the reflect::library! macro for?
It declares the types and functions the generated code will require at runtime, such as the Formatter, Result and DebugStruct types and the Debug trait. All type information in the rest of the macro implementation is then statically inferred from those signatures.
Is reflect ready for production use?
The repository describes itself as a proof of concept, the crate version is 0.0.16, and the description calls it a memorial brand new way of defining procedural macros. The README also warns that the API may change, so an exact version pin is the sensible approach.
How does reflect compare to writing a derive macro with syn and quote?
Both produce equivalent code, and the README says syn and quote are a good fit for maybe 95% of use cases. The difference is the level of abstraction: with reflect the macro author works against a reflect::Value style API and the library handles the low level details, which helps with the harder cases but couples the macro to the library.
What license is reflect released under?
MIT OR Apache-2.0, with both LICENSE-MIT and LICENSE-APACHE at the repository root. The crate requires Rust 1.71 or later, and the docs.rs build targets x86_64-unknown-linux-gnu.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dtolnay-reflect)Was this useful?