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

Call-site example:

const_dispatch!(scrutinee, |const VALUE: ItsType| {
    … VALUE …
})
  • §More advanced usage: the “macro callback” API

    const_dispatch!(scrutinee, ItsType, |$Value:tt| {
        … $Value …
    })

    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 b in this scenario, so prefixing the snippet with let 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 => { /* … */ },
        }
    }
}