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,
) -> R§Examples
§const_dispatch! and bool:
use ::const_dispatch::{
const_dispatch,
primitive::bool, // <- or `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:
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 */ },
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum BinOp {
Add,
Xor,
}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:
#![feature(adt_const_params)]
// 0. Use this crate!
use ::const_dispatch::{const_dispatch, ConstDispatch};
// 1. Make the function "a private helper", and generic over `BinOp`:
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 */ },
}
}
}
// 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`.
// 2. Re-define a "`pub`lic frontend" function
pub fn some_function(b: BinOp, name: &str) {
// This works because `BinOp : ConstDispatch`
const_dispatch!(b, |const B: BinOp| {
some_function_generic::<B>(name)
})
}
// 3. Make sure `BinOp : ConstDispatch`
// vvvvvvvvvvvvv
#[derive(Debug, PartialEq, Eq, ConstDispatch)]
pub enum BinOp {
Add,
Xor,
}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, you could use the type-level enum pattern:
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 VALUE: BinOp; }
enum Add {} impl IsBinOp for Add { const VALUE: BinOp = BinOp::Add; }
enum Xor {} impl IsBinOp for Xor { const VALUE: BinOp = BinOp::Xor; }
enum Sub {} impl IsBinOp for Sub { const VALUE: BinOp = BinOp::Sub; }
enum Mul {} impl IsBinOp for Mul { const VALUE: 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::VALUE {
BinOp::Add => { /* some logic */ },
BinOp::Xor => { /* some other logic */ },
BinOp::Mul => { /* … */ },
BinOp::Sub => { /* … */ },
}
// some common logic
/* some */ loop {
match B::VALUE {
BinOp::Add => { /* some logic */ },
BinOp::Xor => { /* some other logic */ },
BinOp::Mul => { /* … */ },
BinOp::Sub => { /* … */ },
}
}
}