macro_rules! const_dispatch {
(
$scrutinee:expr,
|const $C:ident: $T:ident| $body:block $(,)?
) => { ... };
(
$scrutinee:expr, $T:ident,
|$_:tt $Metavar:ident : $transcriber_kind:ident| $macro_output:tt
) => { ... };
}Expand description
The whole raison d’être of the crate. Statically dispatch a runtime/dynamic value so as to
lift it to the const (and thus, type-level) reälm.
This only works for limited enumerations, such as crate::primitive::bool,
crate::primitive::u8, or custom simple enums.
The types for which this dispatching work is marked by the
ConstDispatch marker trait.
§Usage
The “API” of this macro is described by the following pseudo-code:
macro const_dispatch<T: ConstDispatch>(
scrutinee: T,
const_generic_closure: impl FnOnce(<const C: T>) -> R,
) -> RCall-site example:
const_dispatch!(scrutinee, |const VALUE: ItsType| {
… VALUE …
})§More advanced usage: the “macro callback” API
const_dispatch!(scrutinee, ItsType, |$Value:tt| { … $Value … })- (
:identfor#[derive(ConstDispatch)] enums,:literalfor theprimitives)
For more details, see the example.
- (
§Examples
§const_dispatch! and bool:
use ::const_dispatch::prelude::*;
fn inner<const VERBOSE: bool>() {
// ...
}
fn main() {
let verbose = ::std::env::var("VERBOSE").map_or(false, |s| s == "1");
const_dispatch!(verbose, |const VERBOSE: bool| {
inner::<VERBOSE>()
})
}§Expansion
main in this example just above expands to:
fn main() {
let verbose = ::std::env::var("VERBOSE").map_or(false, |s| s == "1");
match verbose {
| true => {
const VERBOSE: bool = true; // <- the "arg" of the "generic closure",
inner::<VERBOSE>() // <- the body of the "generic closure".
},
| false => {
const VERBOSE: bool = false; // <- the "arg" of the "generic closure",
inner::<VERBOSE>() // <- the body of the "generic closure".
},
}
}§A custom enum
Imagine having:
#[derive(Debug, PartialEq, Eq)]
pub enum BinOp {
Add,
Xor,
}
pub fn some_function(b: BinOp, name: &str) {
match b {
BinOp::Add => { /* some logic */ },
BinOp::Xor => { /* some other logic */ },
}
// some common logic
/* some */ loop {
match b {
BinOp::Add => { /* some more logic */ },
BinOp::Xor => { /* some more other logic */ },
}
}
}This is technically risking to be branching a bunch of times over the value of b.
And rewriting the logic to avoid this may prove to be challenging, or at least non-trivial.
Now, consider instead doing the following simpler transformation:
// 0. Use this crate!
use ::const_dispatch::{const_dispatch, ConstDispatch};
// 1. Make sure `BinOp : ConstDispatch`
// vvvvvvvvvvvvv
#[derive(Debug, PartialEq, Eq, ConstDispatch)]
pub enum BinOp {
Add,
Xor,
}
// 2. use `const_dispatch!` at the beginning of your function
pub fn some_function(b: BinOp, name: &str) {
// This works because `BinOp : ConstDispatch`
const_dispatch!(b, |const B: BinOp| {
// 3. adjust your code to be (const-)matching over the `const B`
// to ensure the branches get optimized out! 🔥
match B {
BinOp::Add => { /* some logic */ },
BinOp::Xor => { /* some other logic */ },
}
// some common logic
/* some */ loop {
match B {
BinOp::Add => { /* some more logic */ },
BinOp::Xor => { /* some more other logic */ },
}
}
})
}This should be easy to do for the developer; and it should be trivial
for the compiler to elide these branches, since B is now const.
-
Remains, however, the risk to be naming the runtime
bin this scenario, so prefixing the snippet withlet b = ();to prevent that might be advisable.The other, ideally cleaner, option, would be to factor out the inner body within a helper function:
ⓘ#![feature(adt_const_params)] // 0. Use this crate! use ::const_dispatch::{const_dispatch, ConstDispatch}; // 1. Make sure `BinOp : ConstDispatch` // vvvvvvvvvvvvv #[derive(Debug, PartialEq, Eq, ConstDispatch)] pub enum BinOp { Add, Xor, } // 2. use `const_dispatch!` at the beginning of your function pub fn some_function(b: BinOp, name: &str) { // This works because `BinOp : ConstDispatch` const_dispatch!(b, |const B: BinOp| { // 2.1 but delegate to a new helper generic function. some_function_generic::<B>(name) }) } // 3. Define the "private helper" *generic over `BinOp`* function. fn some_function_generic<const B: BinOp>(name: &str) { match B { BinOp::Add => { /* some logic */ }, BinOp::Xor => { /* some other logic */ }, } // some common logic /* some */ loop { match B { BinOp::Add => { /* some more logic */ }, BinOp::Xor => { /* some more other logic */ }, } } }Wait,
<const B: BinOp>is not a thing in stable Rust!
True, but you get the idea.
On stable rust, for simple things, using a <const IS_BINOP_ADD: bool> generic instead is
probably the simplest.
Otherwise (e.g. let’s say BinOp has 4 > 2 variants), you could use:
§the type-level enum pattern
Click to hide
use ::const_dispatch::{const_dispatch, ConstDispatch};
#[derive(Debug, PartialEq, Eq, ConstDispatch)]
pub enum BinOp {
Add,
Xor,
Sub,
Mul,
}
// 1. Define some "type-level" `enum` and variants
// (a helper macro could make this a breeze)
trait IsBinOp { const VAL: BinOp; }
enum Add {} impl IsBinOp for Add { const VAL: BinOp = BinOp::Add; }
enum Xor {} impl IsBinOp for Xor { const VAL: BinOp = BinOp::Xor; }
enum Sub {} impl IsBinOp for Sub { const VAL: BinOp = BinOp::Sub; }
enum Mul {} impl IsBinOp for Mul { const VAL: BinOp = BinOp::Mul; }
// 2. Thanks to `const_dispatch!`, dispatch to these
// (using the more advanced "macro rule" API).
pub fn some_function(b: BinOp, name: &str) {
const_dispatch!(b, BinOp, |$Variant:ident| {
some_function_generic::<$Variant>(name)
})
}
// 3. Profit!
fn some_function_generic<B: IsBinOp>(name: &str) {
match B::VAL {
BinOp::Add => { /* some logic */ },
BinOp::Xor => { /* some other logic */ },
BinOp::Mul => { /* … */ },
BinOp::Sub => { /* … */ },
}
// some common logic
/* some */ loop {
match B::VAL {
BinOp::Add => { /* some logic */ },
BinOp::Xor => { /* some other logic */ },
BinOp::Mul => { /* … */ },
BinOp::Sub => { /* … */ },
}
}
}