#tuple #proc-macro #traits #meta-programming #impl #for

macro fortuples

Procedural macros to generalize inherent and trait implementations over tuples

6 releases

0.9.1 Oct 25, 2023
0.9.0 Oct 23, 2022
0.2.0 Oct 16, 2022
0.1.2 Oct 13, 2022

#873 in Procedural macros

Download history 2105/week @ 2024-07-21 4918/week @ 2024-07-28 2257/week @ 2024-08-04 2932/week @ 2024-08-11 3567/week @ 2024-08-18 4867/week @ 2024-08-25 3718/week @ 2024-09-01 2170/week @ 2024-09-08 742/week @ 2024-09-15 891/week @ 2024-09-22 392/week @ 2024-09-29 325/week @ 2024-10-06 321/week @ 2024-10-13 575/week @ 2024-10-20 383/week @ 2024-10-27 459/week @ 2024-11-03

1,752 downloads per month
Used in 11 crates (via pallet-ismp)

MIT license

195KB
6.5K SLoC

fortuples

Procedural macros to generalize inherent and trait implementations over tuples.

Introduction

When it is a need to implement either a trait or a generalized type for a combination of tuples, Rust requires separate implementations to be provided for each tuple variety manually.

This crate provides a proc-macro fortuples! to write code templates similar to the quote! macro. This macro will expand the provided code template for each tuple variety.

Also, an attribute macro #[auto_impl] that implements a given trait for tuple combinations in a completely automatic way.

This crate is inspired by the impl_trait_for_tuples.


Differences from impl_trait_for_tuples

You can write inherent implementations
struct Vector<T>(T);

fortuples! {
    #[tuples::member_type(f32)]
    #[tuples::min_size(2)]
    #[tuples::max_size(3)]
    #[tuples::tuple_name(Coords)]
    impl Vector<#Coords> {
        fn length(&self) -> f32 {
            let coords = &self.0;

            (#(#coords * #coords)+*).sqrt()
        }
    }
}

You don't need to use a custom keyword for_tuples! inside the implementation body

Instead, the fortuples! macro follows the quote!-like syntax without extra tokens.

trait Trait {
    type Ret;
    type Arg;

    fn test(arg: Self::Arg) -> Self::Ret;
}
impl_trait_for_tuples
#[impl_for_tuples(5)]
impl Trait for Tuple {
    for_tuples!( type Ret = ( #( Tuple::Ret ),* ); );
    for_tuples!( type Arg = ( #( Tuple::Arg ),* ); );

    fn test(arg: Self::Arg) -> Self::Ret {
        for_tuples!( ( #( Tuple::test(arg.Tuple) ),* ) )
    }
}
fortuples
fortuples! {
    #[tuples::max_size(5)] // <-- optional, default = 16
    impl Trait for #Tuple
    where
        #(#Member: Trait),*
    {
        type Ret = ( #(#Member::Ret),* );
        type Arg = ( #(#Member::Arg),* );

        fn test(arg: Self::Arg) -> Self::Ret {
            ( #(#Member::test(#arg)),* )
        }
    }
}

Separate attribute macro for full-automatic implementation
impl_trait_for_tuples
#[impl_for_tuples(5)]
trait Notify {
    fn notify(&self);
}
fortuples::auto_impl
#[fortuples::auto_impl]
#[tuples::max_size(5)] // <-- optional, default = 16
trait Notify {
    fn notify(&self);
}

Examples

fortuples! proc-macro

Here is commented example of fortuples! usage.

See the fortuples! macro documentation to learn about the macro settings (like #[tuples::min_size]).

trait Trait {
    type Ret;

    type Arg;

    type FixedType;

    const VALUE: i32;

    const LENGTH: usize;

    fn test_assoc_fn(arg: Self::Arg) -> Self::Ret;

    fn test_self_fn(&self) -> Result<(), ()>;
}

fortuples! {
    #[tuples::min_size(1)]
    // +----- ^^^^^^^^^^^
    // | The `fortuples!` macro will generate implementations starting with the empty tuple.
    // |
    // | Due to the `min_size` setting,
    // | the implementations will start from the `(Member0,)` tuple.

    impl Trait for #Tuple
    // +----------- ^^^^^
    // | a meta-variable that will expand to
    // | `(Member0,)`, `(Member0, Member1)`, and so on.

    where
        #(#Member: Trait<FixedType = i32>),*
    //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // | A repetition -- the code inside the `#(...),*`
    // | will expand as many times as many elements are in the current #Tuple.
    // |
    // | Inside the i-th code fragment, the #Member meta-variable will be substituted
    // | by the i-th member type of the current #Tuple.
    {
        // The `Ret` type will be a tuple consisting of the `Ret` types
        // from the current #Tuple member types
        type Ret = (#(#Member::Ret),*);

        // The `Arg` type will be a tuple consisting of the `Arg` types
        // from the current #Tuple member types
        type Arg = (#(#Member::Arg),*);

        // The `VALUE` will be a sum of all `VALUE`s of the #Tuple member types.
        const VALUE: i32 = #(#Member::VALUE)+*;
        // +------------------------------- ^
        // | Note that a `+` sign separates the `VALUE`s.

        const LENGTH: usize = #len(Tuple);
        // +----------------- ^^^^^^^^^^^
        // | This expands to the current #Tuple length.

        type FixedType = i32;

        fn test_assoc_fn(arg: Self::Arg) -> Self::Ret {
            ( #(#Member::test_assoc_fn(#arg)),* )
            // +----------------------- ^^^
            // | Any identifier after the `#` sign that is neither
            // | #Tuple, #Member, nor #len(Tuple)
            // | is interpreted as a tuple variable.
            // |
            // | So the above code will expand like this:
            // | ```
            // |    (
            // |        Member0::test_assoc_fn(arg.0),
            // |        Member1::test_assoc_fn(arg.1),
            // |        ...
            // |        MemberN::test_assoc_fn(arg.N),
            // |    )
            // | ```
            // | where `N` equals `#len(Tuple)`
        }

        fn test_self_fn(&self) -> Result<(), ()> {
            #(#self.test_self_fn()?;)*
            // +-------------------- ^
            // | Note that there is no separator here.

            Ok(())
        }
    }
}
Show the example without comments

fortuples! proc-macro (without comments)

trait Trait {
    type Ret;

    type Arg;

    type FixedType;

    const VALUE: i32;

    const LENGTH: usize;

    fn test_assoc_fn(arg: Self::Arg) -> Self::Ret;

    fn test_self_fn(&self) -> Result<(), ()>;
}

fortuples! {
    #[tuples::min_size(1)]
    impl Trait for #Tuple
    where
        #(#Member: Trait<FixedType = i32>),*
    {
        type Ret = (#(#Member::Ret),*);

        type Arg = (#(#Member::Arg),*);

        const VALUE: i32 = #(#Member::VALUE)+*;

        const LENGTH: usize = #len(Tuple);

        type FixedType = i32;

        fn test_assoc_fn(arg: Self::Arg) -> Self::Ret {
            ( #(#Member::test_assoc_fn(#arg)),* )
        }

        fn test_self_fn(&self) -> Result<(), ()> {
            #(#self.test_self_fn()?;)*

            Ok(())
        }
    }
}

auto_impl attribute

There is an option to implement a trait in a completely automatic way using the auto_impl attribute.

This attribute will automatically generate implementations of the given trait for tuple combinations.

See the auto_impl documentation to learn about the attribute's settings and limitations.

#[fortuples::auto_impl]
trait AutoImplTrait {
    fn test(&self, a: i32, b: &f32);
}

Dependencies

~1.5MB
~36K SLoC