const_dispatch/
const_dispatch_macro.rs

1#[cfg(doc)]
2use super::*;
3
4/// The whole _raison d'être_ of the crate. Statically dispatch a runtime/dynamic value so as to
5/// lift it to the `const` (and thus, type-level) reälm.
6///
7/// This only works for _limited_ `enum`erations, such as <code>[crate::primitive]::bool</code>,
8/// <code>[crate::primitive]::u8</code>, or [custom simple `enum`s][macro@crate::ConstDispatch].
9///
10/// The types for which this dispatching work is marked by the
11/// [`ConstDispatch`][trait@ConstDispatch] marker trait.
12///
13/// ## Usage
14///
15/// The "API" of this macro is described by the following pseudo-code:
16///
17/// ```rust
18/// # r##"
19/// macro const_dispatch<T: ConstDispatch>(
20///     scrutinee: T,
21///     const_generic_closure: impl FnOnce(<const C: T>) -> R,
22/// ) -> R
23/// # "##;
24/// ```
25///
26/// Call-site example:
27///
28/// ```rust
29/// # r#"
30/// const_dispatch!(scrutinee, |const VALUE: ItsType| {
31///     … VALUE …
32/// })
33/// # "#;
34/// ```
35///
36///   - ### More advanced usage: the "macro callback" API
37///
38///     ```rust
39///     # r#"
40///     const_dispatch!(scrutinee, ItsType, |$Value:tt| {
41///         … $Value …
42///     })
43///     # "#;
44///     ```
45///
46///       - (`:ident` for <code>#\[derive\([ConstDispatch][macro@ConstDispatch]\)\] enum</code>s,
47///         `:literal` for the [`primitive`]s)
48///
49///     For more details, see [the example](#the-type-level-enum-pattern).
50///
51/// ## Examples
52///
53/// ### [`const_dispatch!`] and [`bool`][prim@bool]:
54///
55/// ```rust
56/// use ::const_dispatch::prelude::*;
57///
58/// fn inner<const VERBOSE: bool>() {
59///     // ...
60/// }
61///
62/// fn main() {
63///     let verbose = ::std::env::var("VERBOSE").map_or(false, |s| s == "1");
64///     const_dispatch!(verbose, |const VERBOSE: bool| {
65///         inner::<VERBOSE>()
66///     })
67/// }
68/// ```
69///
70/// ### Expansion
71///
72/// `main` in this example just above expands to:
73///
74/// ```rust, ignore
75/// fn main() {
76///     let verbose = ::std::env::var("VERBOSE").map_or(false, |s| s == "1");
77///     match verbose {
78///         | true => {
79///             const VERBOSE: bool = true; // <- the "arg" of the "generic closure",
80///             inner::<VERBOSE>() // <- the body of the "generic closure".
81///         },
82///         | false => {
83///             const VERBOSE: bool = false; // <- the "arg" of the "generic closure",
84///             inner::<VERBOSE>() // <- the body of the "generic closure".
85///         },
86///     }
87/// }
88/// ```
89///
90/// ### A custom `enum`
91///
92/// Imagine having:
93///
94/// ```rust
95/// #[derive(Debug, PartialEq, Eq)]
96/// pub enum BinOp {
97///     Add,
98///     Xor,
99/// }
100///
101/// pub fn some_function(b: BinOp, name: &str) {
102///     match b {
103///         BinOp::Add => { /* some logic */ },
104///         BinOp::Xor => { /* some other logic */ },
105///     }
106///
107///     // some common logic
108///
109///     /* some */ loop {
110///         match b {
111///             BinOp::Add => { /* some more logic */ },
112///             BinOp::Xor => { /* some more other logic */ },
113///         }
114///     }
115/// }
116/// ```
117///
118/// This is technically risking to be branching a bunch of times over the value of `b`.
119///
120/// And rewriting the logic to avoid this may prove to be challenging, or at least non-trivial.
121///
122/// Now, consider instead doing the following simpler transformation:
123///
124/// ```rust
125/// // 0. Use this crate!
126/// use ::const_dispatch::{const_dispatch, ConstDispatch};
127///
128/// // 1. Make sure `BinOp : ConstDispatch`
129/// //                             vvvvvvvvvvvvv
130/// #[derive(Debug, PartialEq, Eq, ConstDispatch)]
131/// pub enum BinOp {
132///     Add,
133///     Xor,
134/// }
135///
136/// // 2. use `const_dispatch!` at the beginning of your function
137/// pub fn some_function(b: BinOp, name: &str) {
138///     // This works because `BinOp : ConstDispatch`
139///     const_dispatch!(b, |const B: BinOp| {
140///         // 3. adjust your code to be (const-)matching over the `const B`
141///         //    to ensure the branches get optimized out! 🔥
142///         match B {
143///             BinOp::Add => { /* some logic */ },
144///             BinOp::Xor => { /* some other logic */ },
145///         }
146///
147///         // some common logic
148///
149///         /* some */ loop {
150///             match B {
151///                 BinOp::Add => { /* some more logic */ },
152///                 BinOp::Xor => { /* some more other logic */ },
153///             }
154///         }
155///     })
156/// }
157/// ```
158///
159/// This should be easy to do for the developer; and it should be trivial
160/// for the compiler to elide these branches, since `B` is now `const`.
161///
162///   - Remains, however, the risk to be naming the runtime `b` in this scenario, so prefixing
163///     the snippet with `let b = ();` to prevent that might be advisable.
164///
165///     The other, ideally cleaner, option, would be to factor out the inner body within a helper
166///     function:
167///
168///     ```rust ,ignore
169///     #![feature(adt_const_params)]
170///
171///     // 0. Use this crate!
172///     use ::const_dispatch::{const_dispatch, ConstDispatch};
173///
174///     // 1. Make sure `BinOp : ConstDispatch`
175///     //                             vvvvvvvvvvvvv
176///     #[derive(Debug, PartialEq, Eq, ConstDispatch)]
177///     # #[derive(::core::marker::ConstParamTy)]
178///     pub enum BinOp {
179///         Add,
180///         Xor,
181///     }
182///
183///     // 2. use `const_dispatch!` at the beginning of your function
184///     pub fn some_function(b: BinOp, name: &str) {
185///         // This works because `BinOp : ConstDispatch`
186///         const_dispatch!(b, |const B: BinOp| {
187///             // 2.1 but delegate to a new helper generic function.
188///             some_function_generic::<B>(name)
189///         })
190///     }
191///
192///     // 3. Define the "private helper" *generic over `BinOp`* function.
193///     fn some_function_generic<const B: BinOp>(name: &str) {
194///         match B {
195///             BinOp::Add => { /* some logic */ },
196///             BinOp::Xor => { /* some other logic */ },
197///         }
198///
199///         // some common logic
200///
201///         /* some */ loop {
202///             match B {
203///                 BinOp::Add => { /* some more logic */ },
204///                 BinOp::Xor => { /* some more other logic */ },
205///             }
206///         }
207///     }
208///     ```
209///
210///     > _Wait, `<const B: BinOp>` is not a thing in stable Rust!_
211///
212/// True, but you get the idea.
213///
214/// On stable rust, for simple things, **using a `<const IS_BINOP_ADD: bool>` generic** instead is
215/// probably the simplest.
216///
217/// Otherwise (_e.g._ let's say `BinOp` has 4 > 2 variants), you could use:
218///
219/// ### the type-level `enum` pattern
220///
221/// <details class="custom" open><summary><span class="summary-box"><span>Click to hide</span></span></summary>
222///
223/// ```rust
224/// use ::const_dispatch::{const_dispatch, ConstDispatch};
225///
226/// #[derive(Debug, PartialEq, Eq, ConstDispatch)]
227/// pub enum BinOp {
228///     Add,
229///     Xor,
230///     Sub,
231///     Mul,
232/// }
233///
234/// // 1. Define some "type-level" `enum` and variants
235/// //    (a helper macro could make this a breeze)
236/// trait IsBinOp { const VAL: BinOp; }
237///     enum Add {} impl IsBinOp for Add { const VAL: BinOp = BinOp::Add; }
238///     enum Xor {} impl IsBinOp for Xor { const VAL: BinOp = BinOp::Xor; }
239///     enum Sub {} impl IsBinOp for Sub { const VAL: BinOp = BinOp::Sub; }
240///     enum Mul {} impl IsBinOp for Mul { const VAL: BinOp = BinOp::Mul; }
241///
242/// // 2. Thanks to `const_dispatch!`, dispatch to these
243/// //    (using the more advanced "macro rule" API).
244/// pub fn some_function(b: BinOp, name: &str) {
245///     const_dispatch!(b, BinOp, |$Variant:ident| {
246///         some_function_generic::<$Variant>(name)
247///     })
248/// }
249///
250/// // 3. Profit!
251/// fn some_function_generic<B: IsBinOp>(name: &str) {
252///     match B::VAL {
253///         BinOp::Add => { /* some logic */ },
254///         BinOp::Xor => { /* some other logic */ },
255///         BinOp::Mul => { /* … */ },
256///         BinOp::Sub => { /* … */ },
257///     }
258///
259///     // some common logic
260///
261///     /* some */ loop {
262///         # break;
263///         match B::VAL {
264///             BinOp::Add => { /* some logic */ },
265///             BinOp::Xor => { /* some other logic */ },
266///             BinOp::Mul => { /* … */ },
267///             BinOp::Sub => { /* … */ },
268///         }
269///     }
270/// }
271///
272/// # fn main() { some_function(BinOp::Add, ""); }
273/// ```
274///
275/// </details>
276#[macro_export]
277macro_rules! const_dispatch {
278    (
279        $scrutinee:expr,
280        |const $C:ident: $T:ident| $body:block $(,)?
281    ) => ({
282        // Nicer diagnostics if `$T` is not in scope (typo? Missing `use`?) or expects generic params.
283        let _: $T;
284
285        /* NOTE: the following commented out code yields an outstanding error message when `$T!` does
286        not exist. And the path shadowing-or-lack-thereof trick is the usual approach to detect
287        whether a given path is in scope / to have a fallback for when there is not (`default use`).
288        But for some reason, when this pattern:
289            - involves the macro namespace;
290            - and is done in the expansion of a macro (here, `const_dispatch!`)
291                —even with `call_site()` (lack of) hygiene—,
292            then Rust decides to brain-fart and bail. */
293
294        // // This is `default use $crate::ඞ::fallback_const_dispatch! as $T!`.
295        // use self::$T;
296        // #[allow(unused)]
297        // use __better_compile_error::*;
298        // mod __better_compile_error {
299        //     #![allow(warnings)]
300        //     pub use $crate::ඞfallback_const_dispatch as $T;
301        // }
302        /* poorman's version: we will still attempt to call the macro (and expose bad diagnostics), but
303        at least we'll emit the nice error alongside it. */
304        if false {
305            $crate::ඞ::const_dispatchǃ($scrutinee, ())
306        }
307
308        $T!($scrutinee, |const $C| $body)
309    });
310
311    // more general rule exposing a "macro rule" for the output.
312    (
313        $scrutinee:expr, $T:ident,
314        |$_:tt $Metavar:ident : $transcriber_kind:ident| $macro_output:tt
315    ) => ({
316        let _: $T;
317        if false {
318            $crate::ඞ::const_dispatchǃ($scrutinee, ())
319        }
320        $T!($scrutinee, ($_ $Metavar:$transcriber_kind) => $macro_output)
321    });
322}