Skip to main content

reify_reflect_core/
lib.rs

1#![deny(unsafe_code)]
2
3//! # reflect-core
4//!
5//! Core trait definitions for type-level reification and reflection.
6//!
7//! This crate provides the foundational [`Reflect`] trait, the [`reify`]
8//! function, and the [`RuntimeValue`] enum.
9//!
10//! ## The Reification/Reflection Pattern
11//!
12//! This implements the pattern from Kiselyov & Shan's "Functional Pearl:
13//! Implicit Configurations" (popularized by Kmett's Haskell `reflection`
14//! library), adapted to Rust with branded lifetimes for scoping safety.
15//!
16//! - [`Reflect`]: Type-level value → runtime value. A type that encodes
17//!   a value at the type level can produce it at runtime.
18//!
19//! - [`reify`]: Runtime value → scoped type-level context. Takes a runtime
20//!   value and passes it to a continuation as a branded [`Reified`] token.
21//!   The branded lifetime prevents the token from escaping the callback.
22//!
23//! ### Haskell comparison
24//!
25//! In Haskell:
26//! ```haskell
27//! reify :: a -> (forall s. Reifies s a => Proxy s -> r) -> r
28//! reflect :: Reifies s a => proxy s -> a
29//! ```
30//!
31//! In Rust, the `forall s` is modeled by an invariant lifetime `'brand`
32//! on [`Reified`]. The higher-rank bound `for<'brand>` on the callback
33//! ensures the token cannot escape, just as Haskell's rank-2 type
34//! prevents `s` from escaping.
35//!
36//! Unlike Haskell's `reflection` library (which uses `unsafeCoerce` to
37//! fabricate typeclass dictionaries from GHC internals), this implementation
38//! is safe all the way down: no unsafe code, no compiler-internal
39//! assumptions, scoping enforced mechanically by the borrow checker.
40//!
41//! # Examples
42//!
43//! ```
44//! use reify_reflect_core::reify;
45//!
46//! // Lift a runtime value into a scoped type-level context
47//! let result = reify(&42i32, |token| {
48//!     let val: &i32 = token.reflect();
49//!     *val + 1
50//! });
51//! assert_eq!(result, 43);
52//! ```
53
54use std::marker::PhantomData;
55
56/// Converts a type-level value into a runtime value.
57///
58/// Types implementing `Reflect` carry a compile-time value that can be
59/// extracted at runtime via [`Reflect::reflect`].
60///
61/// # Examples
62///
63/// ```
64/// use reify_reflect_core::{Reflect, RuntimeValue};
65///
66/// struct MyZero;
67///
68/// impl Reflect for MyZero {
69///     type Value = RuntimeValue;
70///     fn reflect() -> Self::Value {
71///         RuntimeValue::Nat(0)
72///     }
73/// }
74///
75/// assert_eq!(MyZero::reflect(), RuntimeValue::Nat(0));
76/// ```
77pub trait Reflect {
78    /// The runtime representation of this type-level value.
79    type Value;
80
81    /// Produce the runtime value corresponding to this type-level value.
82    fn reflect() -> Self::Value;
83}
84
85/// A branded token carrying a reified value.
86///
87/// The lifetime `'brand` is existential, created fresh by each call to
88/// [`reify`] and prevented from escaping the callback by the higher-rank
89/// bound `for<'brand>`. This mirrors Haskell's
90/// `forall s. Reifies s a => Proxy s -> r` scoping.
91///
92/// The [`PhantomData`] carrying `fn(&'brand ()) -> &'brand ()`
93/// makes `'brand` *invariant*, preventing the compiler from shrinking
94/// or growing it to unify with any other lifetime. This is what makes
95/// the brand unique.
96///
97/// # Examples
98///
99/// ```
100/// use reify_reflect_core::reify;
101///
102/// reify(&"hello", |token| {
103///     assert_eq!(*token.reflect(), "hello");
104/// });
105/// ```
106///
107/// The token cannot escape:
108///
109/// ```compile_fail
110/// use reify_reflect_core::reify;
111///
112/// let escaped = reify(&42, |token| {
113///     token // ERROR: borrowed data escapes the closure
114/// });
115/// ```
116pub struct Reified<'brand, T: ?Sized> {
117    value: &'brand T,
118    // Invariant in 'brand: prevents lifetime coercion
119    _brand: PhantomData<fn(&'brand ()) -> &'brand ()>,
120}
121
122impl<'brand, T: ?Sized> Reified<'brand, T> {
123    /// Reflect the reified value back to a runtime reference.
124    ///
125    /// This is the Rust equivalent of Haskell's
126    /// `reflect :: Reifies s a => proxy s -> a`.
127    ///
128    /// The branded lifetime ensures this reference cannot outlive the
129    /// [`reify`] callback that created this token.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use reify_reflect_core::reify;
135    ///
136    /// let doubled = reify(&21i32, |token| {
137    ///     token.reflect() * 2
138    /// });
139    /// assert_eq!(doubled, 42);
140    /// ```
141    pub fn reflect(&self) -> &T {
142        self.value
143    }
144}
145
146/// Reify a runtime value into a scoped type-level context.
147///
148/// This is the Rust equivalent of Haskell's
149/// `reify :: a -> (forall s. Reifies s a => Proxy s -> r) -> r`.
150///
151/// The callback receives a [`Reified`] token branded with a fresh lifetime.
152/// The `for<'brand>` bound ensures this lifetime cannot escape the closure,
153/// so the token (and any references derived from it) are confined to the
154/// callback's scope.
155///
156/// **No unsafe code.** Unlike Haskell's `reflection` library (which uses
157/// `unsafeCoerce` to fabricate typeclass dictionaries), this implementation
158/// relies only on Rust's borrow checker for scoping safety.
159///
160/// # Examples
161///
162/// Basic reification and reflection:
163///
164/// ```
165/// use reify_reflect_core::reify;
166///
167/// let result = reify(&100u64, |token| {
168///     token.reflect() + 1
169/// });
170/// assert_eq!(result, 101);
171/// ```
172///
173/// Works with any type:
174///
175/// ```
176/// use reify_reflect_core::reify;
177///
178/// let result = reify(&vec![1, 2, 3], |token| {
179///     token.reflect().iter().sum::<i32>()
180/// });
181/// assert_eq!(result, 6);
182/// ```
183///
184/// Nested reification:
185///
186/// ```
187/// use reify_reflect_core::reify;
188///
189/// let result = reify(&10i32, |outer| {
190///     reify(&20i32, |inner| {
191///         outer.reflect() + inner.reflect()
192///     })
193/// });
194/// assert_eq!(result, 30);
195/// ```
196///
197/// Composing with [`Reflect`]:
198///
199/// ```
200/// use reify_reflect_core::{reify, Reflect, RuntimeValue};
201///
202/// struct Three;
203/// impl Reflect for Three {
204///     type Value = RuntimeValue;
205///     fn reflect() -> RuntimeValue { RuntimeValue::Nat(3) }
206/// }
207///
208/// let result = reify(&Three::reflect(), |token| {
209///     match token.reflect() {
210///         RuntimeValue::Nat(n) => *n * 2,
211///         _ => panic!("expected Nat"),
212///     }
213/// });
214/// assert_eq!(result, 6);
215/// ```
216pub fn reify<T: ?Sized, F, R>(val: &T, f: F) -> R
217where
218    F: for<'brand> FnOnce(Reified<'brand, T>) -> R,
219{
220    // The lifetime of `val` is erased into the fresh `'brand`.
221    // The `for<'brand>` bound on F ensures no `Reified<'brand, T>`
222    // can escape — the caller must be parametric in 'brand.
223    f(Reified {
224        value: val,
225        _brand: PhantomData,
226    })
227}
228
229/// Runtime representation of type-level values.
230///
231/// This enum provides a uniform way to inspect type-level values at runtime,
232/// regardless of their original type-level encoding.
233///
234/// # Examples
235///
236/// ```
237/// use reify_reflect_core::RuntimeValue;
238///
239/// let nat = RuntimeValue::Nat(42);
240/// let boolean = RuntimeValue::Bool(true);
241/// let list = RuntimeValue::List(vec![
242///     RuntimeValue::Nat(1),
243///     RuntimeValue::Nat(2),
244/// ]);
245/// let unit = RuntimeValue::Unit;
246///
247/// assert_eq!(nat, RuntimeValue::Nat(42));
248/// ```
249#[derive(Debug, Clone, PartialEq)]
250pub enum RuntimeValue {
251    /// A natural number.
252    Nat(u64),
253    /// A boolean.
254    Bool(bool),
255    /// A list of runtime values.
256    List(Vec<RuntimeValue>),
257    /// The unit value.
258    Unit,
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264
265    #[test]
266    fn runtime_value_nat() {
267        let val = RuntimeValue::Nat(42);
268        assert_eq!(val, RuntimeValue::Nat(42));
269    }
270
271    #[test]
272    fn runtime_value_bool() {
273        let val = RuntimeValue::Bool(true);
274        assert_eq!(val, RuntimeValue::Bool(true));
275    }
276
277    #[test]
278    fn runtime_value_list() {
279        let val = RuntimeValue::List(vec![RuntimeValue::Nat(1), RuntimeValue::Nat(2)]);
280        assert_eq!(
281            val,
282            RuntimeValue::List(vec![RuntimeValue::Nat(1), RuntimeValue::Nat(2)])
283        );
284    }
285
286    #[test]
287    fn runtime_value_unit() {
288        assert_eq!(RuntimeValue::Unit, RuntimeValue::Unit);
289    }
290
291    #[test]
292    fn runtime_value_clone() {
293        let val = RuntimeValue::List(vec![RuntimeValue::Bool(false)]);
294        let cloned = val.clone();
295        assert_eq!(val, cloned);
296    }
297
298    struct TestZero;
299
300    impl Reflect for TestZero {
301        type Value = RuntimeValue;
302        fn reflect() -> Self::Value {
303            RuntimeValue::Nat(0)
304        }
305    }
306
307    #[test]
308    fn reflect_trait_works() {
309        assert_eq!(TestZero::reflect(), RuntimeValue::Nat(0));
310    }
311
312    #[test]
313    fn reify_basic() {
314        let result = reify(&42i32, |token| *token.reflect() + 1);
315        assert_eq!(result, 43);
316    }
317
318    #[test]
319    fn reify_string() {
320        let s = String::from("hello");
321        let len = reify(&s, |token| token.reflect().len());
322        assert_eq!(len, 5);
323    }
324
325    #[test]
326    fn reify_nested() {
327        let result = reify(&10i32, |outer| {
328            reify(&20i32, |inner| outer.reflect() + inner.reflect())
329        });
330        assert_eq!(result, 30);
331    }
332
333    #[test]
334    fn reify_vec() {
335        let data = vec![1, 2, 3, 4, 5];
336        let sum = reify(&data, |token| token.reflect().iter().sum::<i32>());
337        assert_eq!(sum, 15);
338    }
339
340    #[test]
341    fn reify_with_reflect_trait() {
342        let reflected = TestZero::reflect();
343        let result = reify(&reflected, |token| match token.reflect() {
344            RuntimeValue::Nat(n) => *n,
345            _ => panic!("expected Nat"),
346        });
347        assert_eq!(result, 0);
348    }
349
350    #[test]
351    fn reify_dyn_trait() {
352        // Works with unsized types via &dyn
353        trait Greet {
354            fn greet(&self) -> &str;
355        }
356        struct Hello;
357        impl Greet for Hello {
358            fn greet(&self) -> &str {
359                "hi"
360            }
361        }
362
363        let obj: &dyn Greet = &Hello;
364        let result = reify(obj, |token| token.reflect().greet().to_string());
365        assert_eq!(result, "hi");
366    }
367
368    #[test]
369    fn reify_slice() {
370        let data = [1, 2, 3];
371        let sum = reify(&data[..], |token| token.reflect().iter().sum::<i32>());
372        assert_eq!(sum, 6);
373    }
374}