Giter VIP home page Giter VIP logo

specialized-dispatch's Introduction

specialized-dispatch

Github Repository crates.io Version docs.rs Documentation Github Actions Build

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.
        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 Extra Arguments

Extra arguments can be passed to specializations. 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");
    println!("Done!");
}

Specialization still happens based on the first argument only.

As with previous examples, the example above is included in the repository as well. It can be run with cargo run --example pass_args or inspected with cargo-expand.

Advanced Serdelike Example

Let's say you are implementing a deserializer. There might be certain types that work well with your own deserializer, while they have a default implementation for generic deserializers (or even unimplemented! by default).

To simplify the example, we will create a watered-down version of relevant serde traits.

#![feature(min_specialization)]

use specialized_dispatch::specialized_dispatch;

/// A simplified version of `serde::de::Deserializer`.
trait Deserializer<'de> {
    type Error;

    // Some generic deserializer functions...
}

/// A simplified version of `serde::de::Deserialize`.
trait Deserialize<'de>: Sized {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>;
}

/// The node type we want to deserialize.
#[derive(Debug)]
struct MyAwesomeNode;

/// Our custom deserializer.
struct MyAwesomeDeserializer;

impl MyAwesomeDeserializer {
    fn my_awesome_function(&mut self) -> MyAwesomeNode {
        MyAwesomeNode
    }
}

impl Deserializer<'_> for MyAwesomeDeserializer {
    type Error = ();
    // Implement the generic deserializer functions...
}

impl<'de> Deserialize<'de> for MyAwesomeNode {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        Ok(specialized_dispatch! {
            D -> MyAwesomeNode,
            // TODO(ozars): This causes rustc ICE.
            // default fn <'de, T: Deserializer<'de>>(_deserializer: T) => {
            default fn <T>(_deserializer: T) => {
                unimplemented!()
            },
            fn (mut deserializer: MyAwesomeDeserializer) => {
                // We can call a method from the concrete implementation here!
                deserializer.my_awesome_function()
            },
            deserializer
        })
    }
}

fn main() {
    println!("{:?}", MyAwesomeNode::deserialize(MyAwesomeDeserializer));
}

The example above is included in the repository. It can be run with cargo run --example serdelike_example 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 Extra Arguments section.

Not working well with lifetimes

I tried implementing lifetime support in various places, but I hit some compiler errors and in some cases Internal Compiler Errors (ICE). See TODO in Advanced Serdelike Example.

This is very likely due to underlying min_specialization implementation not being very mature yet, though it's quite possible I botched something somewhere (Please file an issue if you figure out which :P).

See also

  • sagebind/castaway: Safe, zero-cost downcasting for limited compile-time specialization. This is an awesome library I've just stumbled upon, which seems to be doing what is done here in a more robust and stable way.
  • Autoref-based stable specialization: A detailed case study for various methods of specialization, introducing "autoref-based specalization".
  • Generalized Autoref-Based Specialization: Another method called "autoderef-based specialization" which builds on top of the article above.

specialized-dispatch's People

Contributors

ozars avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.