reify-reflect
A unified ecosystem of small Rust crates for moving values back and forth between the type level and the value level, safely and ergonomically.
If you've ever wanted to:
- Read out a number, boolean, or struct shape encoded in the type system as a normal runtime value,
- Take a runtime value (a number from a config file, a length from user input) and "lift" it into the type system as a const generic,
- Reify a graph of
Rc/Arcpointers into something you can serialize, inspect, or rebuild, - Swap out a type's
Ord,Hash, orDisplayimplementation for a single block of code, - Or extract the structure of an
asyncfunction as an inspectable step graph,
...this is the workspace for that. Everything is #![deny(unsafe_code)]. There is no unsafe, no compiler-internal layout assumptions, and no unsafeCoerce-flavored tricks. Where Haskell's reflection library leans on GHC internals, this leans on the borrow checker.
What "reification" and "reflection" mean here
Two directions, both useful:
- Reflection: type → value. A type like
S<S<S<Z>>>carries the number 3 at compile time.Three::reflect()hands you3at runtime. - Reification: value → type. You have a
u64that is only known at runtime, and you want to use it where aconst N: u64is required.reify_nat(n, &cb)enters a callback in whichNreally is a const generic, monomorphized at compile time.
Together they let you move freely between Rust's type system and its values without leaving safe code.
Quick taste
use ;
use ;
// Type → value
type Three = ;
assert_eq!;
use reify_nat_fn;
// Value → type → value. Inside the closure, `n` is the const-generic
// monomorphization that matches the runtime input.
let squared = reify_nat_fn;
assert_eq!;
A longer, narrative tour lives in docs/blog-post.md, and step-by-step guides are in docs/guides/.
Where to start
Pick the entry point that matches what you're trying to do:
| You want to... | Start with | Read |
|---|---|---|
| Understand the whole pattern | reify-reflect-core + reflect-nat |
Guide 1: Reflect basics |
Use a runtime value as a const N: u64 |
const-reify (+ const-reify-derive) |
Guide 3: const-reify, Guide 4: #[reifiable] |
| Lift any value into a scoped type-level context | reify-reflect-core::reify |
Guide 2: branded reify |
Serialize an Rc<RefCell<T>> graph |
reify-graph |
reify-graph/examples/serialize_graph.rs |
Override Ord/Hash/Display for one block |
context-trait |
docs.rs page |
Inspect what an async fn is doing |
async-reify |
async-reify/examples/trace_workflow.rs |
| Try the whole stack end to end | facade reify-reflect |
examples/roundtrip.rs |
If you just want one dependency that re-exports everything, the facade crate is reify-reflect. If you want a leaner build, depend on the individual crates directly.
Tour of the crates
reify-reflect facade: re-exports everything below
├── reify-reflect-core Reflect trait, reify(), Reified token, RuntimeValue
├── reflect-nat Peano naturals, type-level booleans, HLists
├── reflect-derive #[derive(Reflect)] for structs and enums
├── reify-graph Rc<RefCell<T>> / Arc<Mutex<T>> graph reify+reflect
├── context-trait Scoped Ord/Hash/Display swaps via WithContext
├── async-reify Trace and reify async execution as a step graph
├── async-reify-macros #[trace_async] attribute proc macro
├── const-reify Safe runtime u64 → const generic dispatch (0..=255)
└── const-reify-derive #[reifiable] proc macro for trait dispatch tables
Every crate has its own docs.rs page with a runnable example at the top. The phase design notes in docs/ explain why each crate is shaped the way it is.
A few more flavors
Derive Reflect on your own types
use Reflect;
use ;
use ;
Reify a pointer graph for serialization
use ;
use RefCell;
use Rc;
let leaf = new;
let root = new;
let graph = reify_graph;
// `graph` is now a flat node+edge structure you can serialize, inspect, or transform
let restored = reflect_graph;
Swap a trait implementation for one scope
use ;
let items = vec!;
with_ord!;
Trace an async workflow
use ;
# block_on;
Use a runtime value as a const generic
use ;
;
let n: u64 = 7; // runtime input
assert_eq!; // dispatched into call::<7>
For traits with multiple const-generic methods, #[reifiable(range = 0..=255)] on the trait declaration generates the dispatch wrappers automatically. See Guide 4.
Feature flags
| Feature | Default | Effect |
|---|---|---|
serde |
yes | Serialize/Deserialize for reify-graph and async-reify types |
const-reify |
no | Re-export the const_bridge module (adds 256 monomorphizations to compile time) |
typenum |
no | Bridge between reflect-nat and the typenum crate |
frunk |
no | Bridge between reflect-nat and frunk's HList |
full |
no | All of the above |
Documentation
- API docs:
cargo doc --features full --no-deps --open - Step-by-step tutorials:
docs/guides/ - Narrative overview:
docs/blog-post.md - Per-phase design notes:
docs/phase1-foundations.md...docs/phase6-integration.md - Forward-looking RFCs:
docs/rfcs/ - End-to-end example:
examples/roundtrip.rs(cargo run --example roundtrip --features full)
Benchmarks
The headline numbers (Criterion, on the maintainer's laptop): HList reflection is ~free, graph reification scales linearly to thousands of nodes, and WithContext-based sorts are within noise of plain closure sorts. Treat these as smoke tests, not promises: re-run them on your hardware before relying on them.
How this compares to Haskell's reflection
Haskell reflection |
reify-reflect |
|
|---|---|---|
| Scoping mechanism | Rank-2 forall s |
for<'brand> lifetimes / NatCallback trait |
| Internal safety | unsafeCoerce |
No unsafe anywhere |
| Range | Unbounded | 0..=255 per dispatch (extensible by composition) |
| Closures over the brand | Yes | Trait impl, or one of the #[reifiable] / reify_nat_fn shortcuts |
| Cost model | Dictionary lookup per call | Direct, fully monomorphized dispatch |
The Rust version trades unbounded range for monomorphized performance and a fully safe surface area.
Status
Version 0.1.0 covers the six phases listed in CHANGELOG.md. The API is consistent and tested, but this is still an early release. Expect breaking changes before 1.0, and please file issues if anything in the docs is unclear or wrong.
License
Dual licensed under MIT or Apache 2.0, at your option.