Skip to main content

const_reify/
lib.rs

1#![deny(unsafe_code)]
2
3//! Use a runtime `u64` as a `const N: u64`, safely.
4//!
5//! Const generics in Rust have to be known at compile time. That's
6//! frustrating when the value you actually want to parameterize on
7//! (a modulus, a buffer size, a feature flag) only becomes known at
8//! runtime. The orthodox workarounds are to drop the const generic
9//! (and lose the type safety), or to write a giant `match` by hand.
10//!
11//! This crate is the giant `match`, generated for you, with three
12//! progressively more powerful APIs on top.
13//!
14//! Everything is `#![deny(unsafe_code)]`. There is no vtable
15//! fabrication, no `transmute`, no UB. The dispatch is a flat 256-arm
16//! `match` that the compiler optimizes well. The tradeoff: the runtime
17//! value must lie in `0..=255` per dispatch (and the trait this crate
18//! ships with is just one example; you can build your own with a wider
19//! range). For the full safety analysis and why we chose this over the
20//! original "fabricate a vtable" approach, see [`DESIGN.md`][design].
21//!
22//! [design]: https://github.com/joshburgess/reify-reflect/blob/main/const-reify/DESIGN.md
23//!
24//! # Three APIs, in order of power
25//!
26//! ## 1. [`reify_const`] / [`reify!`]
27//!
28//! Smallest surface area. You get a `&dyn HasModulus` whose
29//! [`modulus()`](HasModulus::modulus) returns your runtime value. The
30//! const generic is "real" inside the dispatch (each arm calls
31//! `Modular::<N>::new()`), but you can only see it through the
32//! [`HasModulus`] trait. Useful for testing the wiring.
33//!
34//! ```
35//! use const_reify::{reify_const, HasModulus};
36//!
37//! let result = reify_const(17, |m| m.modulus());
38//! assert_eq!(result, 17);
39//! ```
40//!
41//! ## 2. [`reify_nat_fn`] / [`reify_nat2_fn`]
42//!
43//! When you only need the runtime value as a plain `u64` inside the
44//! callback (no const generic gymnastics needed). The closure form is
45//! the easiest entry point and is enough for most ad-hoc uses.
46//!
47//! ```
48//! use const_reify::reify_nat_fn;
49//!
50//! let squared = reify_nat_fn(12, |n| n * n);
51//! assert_eq!(squared, 144);
52//! ```
53//!
54//! ## 3. [`NatCallback`] / [`reify_nat`]
55//!
56//! The full power form. Implement [`NatCallback`] on a type, and inside
57//! [`call::<const N: u64>()`](NatCallback::call) the value `N` is a
58//! genuine const generic that you can use in `const N: u64` positions.
59//!
60//! ```
61//! use const_reify::nat_reify::{NatCallback, reify_nat};
62//!
63//! struct Square;
64//! impl NatCallback<u64> for Square {
65//!     fn call<const N: u64>(&self) -> u64 { N * N }
66//! }
67//!
68//! assert_eq!(reify_nat(7, &Square), 49);
69//! ```
70//!
71//! For traits with multiple const-generic methods, the
72//! [`#[reifiable]`](https://docs.rs/const-reify-derive) proc macro
73//! generates the `NatCallback` plumbing automatically. See
74//! [Guide 4][guide4].
75//!
76//! [guide4]: https://github.com/joshburgess/reify-reflect/blob/main/docs/guides/04-reifiable-macro.md
77//!
78//! # See also
79//!
80//! - [`docs/phase5-const-reify.md`][phase5] for the design rationale,
81//!   including why the range is 256 and why this is fully safe despite
82//!   the "vtable fabrication" reputation of the underlying technique.
83//! - The [narrative blog post][blog] for a worked example using modular
84//!   arithmetic.
85//!
86//! [phase5]: https://github.com/joshburgess/reify-reflect/blob/main/docs/phase5-const-reify.md
87//! [blog]: https://github.com/joshburgess/reify-reflect/blob/main/docs/blog-post.md
88
89mod dispatch;
90pub mod nat_reify;
91
92pub use dispatch::{reify_const, HasModulus, Modular};
93pub use nat_reify::{
94    reify_nat, reify_nat2, reify_nat2_fn, reify_nat_fn, FnNat, FnNat2, Nat2Callback, NatCallback,
95};
96
97/// Maximum supported value for [`reify_const`] dispatch.
98pub const MAX_REIFY_VALUE: u64 = 255;
99
100/// Convenience macro for runtime-to-const-generic dispatch.
101///
102/// # Examples
103///
104/// ```
105/// use const_reify::{reify, HasModulus};
106///
107/// reify!(10u64, |m: &dyn HasModulus| {
108///     assert_eq!(m.modulus(), 10);
109/// });
110/// ```
111///
112/// # Panics
113///
114/// Panics if the value exceeds [`MAX_REIFY_VALUE`] (255).
115#[macro_export]
116macro_rules! reify {
117    ($val:expr, $f:expr) => {{
118        $crate::reify_const($val, $f)
119    }};
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn reify_zero() {
128        let result = reify_const(0, |m| m.modulus());
129        assert_eq!(result, 0);
130    }
131
132    #[test]
133    fn reify_max() {
134        let result = reify_const(255, |m| m.modulus());
135        assert_eq!(result, 255);
136    }
137
138    #[test]
139    fn reify_arbitrary() {
140        for v in [1, 17, 42, 100, 200, 255] {
141            let result = reify_const(v, |m| m.modulus());
142            assert_eq!(result, v);
143        }
144    }
145
146    #[test]
147    #[should_panic(expected = "out of supported range")]
148    fn reify_out_of_range() {
149        reify_const(256, |m| m.modulus());
150    }
151
152    #[test]
153    fn reify_macro() {
154        reify!(42u64, |m: &dyn HasModulus| { assert_eq!(m.modulus(), 42) });
155    }
156
157    #[test]
158    fn reify_returns_value() {
159        let doubled = reify_const(21, |m| m.modulus() * 2);
160        assert_eq!(doubled, 42);
161    }
162
163    #[test]
164    fn reify_all_values() {
165        for v in 0..=255u64 {
166            assert_eq!(reify_const(v, |m| m.modulus()), v);
167        }
168    }
169}