specialized-dispatch 0.2.0

A library for dispatching specialized versions of a function
Documentation

This crate provides a procedural macro, specialized_dispatch, a convenient way to implement different behaviors based on type of an expression.

This works by creating different specializations in the callsite by making use of min_specialization nightly feature under the hood.

As such, the caller needs to enable this nightly feature for the library from which this macro is called.

Simple Example

#![feature(min_specialization)]

use specialized_dispatch::specialized_dispatch;

fn example<E>(expr: E) -> String {
    specialized_dispatch!(
        // Type of the expression -> return type.
        E -> String,
        // Defaut implementation. At least one default value is required.
        // Referring to values other than the provided argument is not
        // supported.
        default fn <T>(_: T) => format!("default value"),
        // Specialization for concrete type u8.
        fn (v: u8) => format!("u8: {}", v),
        // Specialization for concrete type u16.
        fn (v: u16) => format!("u16: {}", v),
        // The expression for passing to above specializations.
        expr,
    )
}

fn main() {
    assert_eq!(example(1.0), "default value");
    assert_eq!(example(5u8), "u8: 5");
    assert_eq!(example(10u16), "u16: 10");
    println!("Done!");
}

example function roughly expands to below code. Note that exact expansion is internal implementation detail. This example is provided to demonstrate how it works under the hood.

fn example<E>(expr: E) -> String {
    trait SpecializedDispatchCall<T> {
        fn dispatch(t: T) -> String;
    }

    impl<T> SpecializedDispatchCall<T> for T {
        default fn dispatch(_: T) -> String {
            format!("default value")
        }
    }

    impl SpecializedDispatchCall<u8> for u8 {
        fn dispatch(v: u8) -> String {
            format!("u8: {}", v)
        }
    }

    impl SpecializedDispatchCall<u8> for u16 {
        fn dispatch(v: u16) -> String {
            format!("u16: {}", v)
        }
    }

    <E as SpecializedDispatchCall<E>>::dispatch(expr)
}

The example above is included in the repository.

It can be run with cargo run --example simple_example.

Expanded code can be inspected using cargo-expand: cargo expand --example simple_example.

Trait Bounds

Trait bounds can be provided for the default case:

#![feature(min_specialization)]

use std::fmt::Display;

use specialized_dispatch::specialized_dispatch;

// The expression type must also bind to the same trait.
fn example<E: Display>(expr: E) -> String {
    specialized_dispatch!(
        E -> String,
        // Notice the trait bound.
        default fn <T: Display>(v: T) => format!("default value: {}", v),
        // Note that specializations also need to satisfy the same bound.
        fn (v: u8) => format!("u8: {}", v),
        fn (v: u16) => format!("u16: {}", v),
        expr,
    )
}

fn main() {
    assert_eq!(example(1.5), "default value: 1.5");
    assert_eq!(example(5u8), "u8: 5");
    assert_eq!(example(10u16), "u16: 10");
    println!("Done!");
}

Likewise, the example above is included in the repository.

It can be run with cargo run --example trait_bound or inspected with cargo-expand.

Passing Arguments

Arguments can be passed to specializations, however, argument types need to declared explicitly (i.e. they won't be captured automatically as it happens with closures).

#![feature(min_specialization)]

use std::fmt::Display;

use specialized_dispatch::specialized_dispatch;

fn example<T: Display>(expr: T, arg: &str) -> String {
    specialized_dispatch!(
        T -> String,
        default fn <T: Display>(v: T, arg: &str) => {
            format!("default value: {}, arg: {}", v, arg)
        },
        fn (v: u8, arg: &str) => format!("u8: {}, arg: {}", v, arg),
        fn (v: u16, arg: &str) => format!("u16: {}, arg: {}", v, arg),
        expr, arg,
    )
}

fn main() {
    assert_eq!(example(1.5, "I'm a"), "default value: 1.5, arg: I'm a");
    assert_eq!(example(5u8, "walnut"), "u8: 5, arg: walnut");
    assert_eq!(example(10u16, "tree"), "u16: 10, arg: tree");
}

Likewise, the example above is included in the repository.

It can be run with cargo run --example pass_args or inspected with cargo-expand.

Limitations

Requires nightly

This is due to relying on min_specialization feature.

Only concrete types are supported for specialization

Specialization can be used only with concrete types (e.g. subtraits cannot be used for specialization). This is an existing limitation inherited from the current implementation of min_specialization feature.

Variables aren't captured automatically

The macro expands its arms to some method implementations. As such, it cannot refer to other variables in the scope where it's called from.

However, extra arguments can be passed when they are explicitly declared in the macro. Please refer to Passing Arguments section.