const_dispatch

Macro const_dispatch

Source
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 => { /* … */ },
        }
    }
}