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}