typle 0.7.1

A macro to create items for different sized tuples
Documentation

typle

Rust-ic manipulation of tuples.

An Initial Example

The implementation of the Hash trait for tuples simply hashes each component of the tuple.

In the standard library the implementation (without docs) looks like this:

macro_rules! impl_hash_tuple {
    () => (
        impl Hash for () {
            #[inline]
            fn hash<H: Hasher>(&self, _state: &mut H) {}
        }
    );

    ( $($name:ident)+) => (
        impl<$($name: Hash),+> Hash for ($($name,)+) where last_type!($($name,)+): ?Sized {
            #[allow(non_snake_case)]
            #[inline]
            fn hash<S: Hasher>(&self, state: &mut S) {
                let ($(ref $name,)+) = *self;
                $($name.hash(state);)+
            }
        }
    );
}

macro_rules! last_type {
    ($a:ident,) => { $a };
    ($a:ident, $($rest_a:ident,)+) => { last_type!($($rest_a,)+) };
}

impl_hash_tuple! {}
impl_hash_tuple! { T }
impl_hash_tuple! { T B }
impl_hash_tuple! { T B C }
impl_hash_tuple! { T B C D }
impl_hash_tuple! { T B C D E }
impl_hash_tuple! { T B C D E F }
impl_hash_tuple! { T B C D E F G }
impl_hash_tuple! { T B C D E F G H }
impl_hash_tuple! { T B C D E F G H I }
impl_hash_tuple! { T B C D E F G H I J }
impl_hash_tuple! { T B C D E F G H I J K }
impl_hash_tuple! { T B C D E F G H I J K L }

Using typle the same implementation can be made shorter and clearer:

impl Hash for () {
    #[inline]
    fn hash<H: Hasher>(&self, _state: &mut H) {}
}

#[typle(Tuple for 1..=12)]
impl<T> Hash for T
where
    T: Tuple,
    T<_>: Hash,
    T<{T::LEN - 1}>: ?Sized,
{
    #[inline]
    fn hash<S: Hasher>(&self, state: &mut S) {
        for typle_const!(i) in 0..T::LEN {
            self[[i]].hash(state);
        }
    }
}

A Quick Introduction

The typle! macro can generate trait implementations for multiple tuple lengths:

use typle::typle;

struct MyStruct<T> {
    t: T,
}

#[typle(Tuple for 0..=3)]
impl<T> From<T> for MyStruct<T>
where
    T: Tuple,
{
    fn from(t: T) -> Self {
        MyStruct { t }
    }
}

This generates implementations of the From trait for tuples with 0 to 3 components:

impl From<()> for MyStruct<()> {
    fn from(t: ()) -> Self {
        MyStruct { t }
    }
}

impl<T0> From<(T0,)> for MyStruct<(T0,)> {
    fn from(t: (T0,)) -> Self {
        MyStruct { t }
    }
}

impl<T0, T1> From<(T0, T1)> for MyStruct<(T0, T1)> {
    fn from(t: (T0, T1)) -> Self {
        MyStruct { t }
    }
}

impl<T0, T1, T2> From<(T0, T1, T2)> for MyStruct<(T0, T1, T2)> {
    fn from(t: (T0, T1, T2)) -> Self {
        MyStruct { t }
    }
}

Inside typle code, select individual components of a tuple using <{i}> for types and [[i]] for values.

The typle_for! macro creates a new tuple type or value.

In where clauses, <_> refers to each component type of the tuple. The typle_bound! macro is more general and allows the iteration value to be used in the trait bounds.

use std::ops::Mul;

#[typle(Tuple for 1..=3)]
impl<T> MyStruct<T>
where
    T: Tuple,
    T<_>: Copy,
{
    // Return a tuple containing all components except the first
    fn tail(&self) -> typle_for!(i in 1.. => T<{i}>) {
        typle_for!(i in 1.. => self.t[[i]])
    }

    // Return a MyStruct containing the product of the components of two tuples
    fn multiply<M>(
        &self, multipliers: M
    ) -> MyStruct<typle_for!(i in .. => <T<{i}> as Mul<M<{i}>>>::Output)>
    where
        M: Tuple,
        typle_bound!(i in .. => T<{i}>): Mul<M<{i}>>,
    {
        typle_for!(i in .. => self.t[[i]] * multipliers[[i]]).into()
    }
}

Generated implementation for 3-tuples:

impl<T0, T1, T2> MyStruct<(T0, T1, T2)>
where
    T0: Copy,
    T1: Copy,
    T2: Copy,
{
    fn tail(&self) -> (T1, T2) {
        (self.t.1, self.t.2)
    }

    fn multiply<M0, M1, M2>(
        &self,
        multipliers: (M0, M1, M2),
    ) -> MyStruct<(
        <T0 as Mul<M0>>::Output,
        <T1 as Mul<M1>>::Output,
        <T2 as Mul<M2>>::Output,
    )>
    where
        T0: Mul<M0>,
        T1: Mul<M1>,
        T2: Mul<M2>,
    {
        (
            self.t.0 * multipliers.0,
            self.t.1 * multipliers.1,
            self.t.2 * multipliers.2
        ).into()
    }
}

The typle trait can take a type parameter when all components have the same type.

The associated constant LEN provides the length of the tuple in each generated item.

Use the typle_const! macro to perform const-for, iterating over a block of statements.

#[typle(Tuple for 1..=3)]
impl<T, C> MyStruct<T>
where
    T: Tuple<C>,
    C: for<'a> std::ops::AddAssign<&'a C> + Default,
{
    // Return a reference to the last component of the tuple
    fn last(&self) -> &T<{T::LEN - 1}> {
        &self.t[[T::LEN - 1]]
    }

    // Return the sums of all even positions and all odd positions
    fn interleave(&self) -> (C, C) {
        let mut even_odd = (C::default(), C::default());
        for typle_const!(i) in 0..T::LEN {
            even_odd[[i % 2]] += &self.t[[i]];
        }
        even_odd
    }
}

Generated implementation for 3-tuples:

impl<C> MyStruct<(C, C, C)>
where
    C: for<'a> std::ops::AddAssign<&'a C> + Default,
{
    fn last(&self) -> &C {
        &self.t.2
    }

    fn interleave(&self) -> (C, C) {
        let mut even_odd = (C::default(), C::default());
        {
            {
                even_odd.0 += &self.t.0;
            }
            {
                even_odd.1 += &self.t.1;
            }
            {
                even_odd.0 += &self.t.2;
            }
            ()
        }
        even_odd
    }
}

The typle macro can be applied to items other than impls. A standalone zip function to pair up the components of two tuples:

#[typle(Tuple for 0..=12)]
fn zip<A, B>(a: A, b: B) -> typle_for!(i in .. => (A<{i}>, B<{i}>))
where
    A: Tuple,
    B: Tuple,
{
    typle_for!(i in .. => (a[[i]], b[[i]]))
}

The generated code uses a hidden trait to achieve a limited form of overloading:

#[allow(non_camel_case_types)]
trait _typle_fn_zip {
    type Return;
    fn apply(self) -> Self::Return;
}

fn zip<A, B>(first: A, second: B) -> <(A, B) as _typle_fn_zip>::Return
where
    (A, B): _typle_fn_zip,
{
    <(A, B) as _typle_fn_zip>::apply((first, second))
}

impl<A0, A1, A2, B0, B1, B2> _typle_fn_zip for ((A0, A1, A2), (B0, B1, B2)) {
    type Return = ((A0, B0), (A1, B1), (A2, B2));
    fn apply(self) -> Self::Return {
        let (first, second) = self;
        { ((first.0, second.0), (first.1, second.1), (first.2, second.2)) }
    }
}

The following example, simplified from code in the hefty crate, shows typle applied to an enum using the typle_variant! macro. Note the use of T<{..}> and typle_index! when referring to a typle struct or enum. This is required because these items require a numeric suffix: TupleSequenceState0, TupleSequenceState1,...

The typle_const! macro also supports const-if on an expression that evaluates to a bool. const-if allows branches that do not compile, as long as they are false at compile-time. For example, this code compiles when i + 1 == T::LEN even though the state S::<typle_index!(i + 1)> (S3 for 3-tuples) is not defined.

#[typle(Tuple for 1..=3)]
mod tuple {
    pub trait Extract {
        type State;
        type Output;

        fn extract(&self, state: Option<Self::State>) -> Self::Output;
    }

    pub enum TupleSequenceState<T>
    where
        T: Tuple,
        T<_>: Extract,
    {
        S = typle_variant!(i in .. =>
            typle_for!(j in ..i => T::<{j}>::Output),
            Option<T<{i}>::State>
        ),
    }

    pub struct TupleSequence<T> {
        tuple: T,
    }

    impl<T> Extract for TupleSequence<T>
    where
        T: Tuple,
        T<_>: Extract,
    {
        type State = TupleSequenceState<T<{..}>>;
        type Output = typle_for!(i in .. => T<{i}>::Output);

        fn extract(&self, state: Option<Self::State>) -> Self::Output {
            #[allow(unused_mut)]  // For LEN = 1 `state` is never mutated
            let mut state = state.unwrap_or(Self::State::S::<typle_index!(0)>((), None));
            for typle_const!(i) in 0..T::LEN {
                // For LEN = 1 there is only one state and the initial `output` variable is unused
                #[allow(irrefutable_let_patterns, unused_variables)]
                if let Self::State::S::<typle_index!(i)>(output, inner_state) = state {
                    let matched = self.tuple[[i]].extract(inner_state);
                    let output = typle_for!(j in ..=i =>
                        if typle_const!(j != i) { output[[j]] } else { matched }
                    );
                    if typle_const!(i + 1 == T::LEN) {
                        return output;
                    } else {
                        state = Self::State::S::<typle_index!(i + 1)>(output, None);
                    }
                }
            }
            unreachable!();
        }
    }
}

Generated implementation for 3-tuples:

pub enum TupleSequenceState3<T0, T1, T2>
where
    T0: Extract,
    T1: Extract,
    T2: Extract,
{
    S0((), Option<<T0>::State>),
    S1((<T0>::Output,), Option<<T1>::State>),
    S2((<T0>::Output, <T1>::Output), Option<<T2>::State>),
}

impl<T0, T1, T2> Extract for TupleSequence<(T0, T1, T2)>
where
    T0: Extract,
    T1: Extract,
    T2: Extract,
{
    type State = TupleSequenceState3<T0, T1, T2>;
    type Output = (<T0>::Output, <T1>::Output, <T2>::Output);
    fn extract(&self, state: Option<Self::State>) -> Self::Output {
        #[allow(unused_mut)]
        let mut state = state.unwrap_or(Self::State::S0((), None));
        {
            {
                #[allow(irrefutable_let_patterns, unused_variables)]
                if let Self::State::S0(output, inner_state) = state {
                    let matched = self.tuple.0.extract(inner_state);
                    let output = ({ matched },);
                    {
                        state = Self::State::S1(output, None);
                    }
                }
            }
            {
                #[allow(irrefutable_let_patterns, unused_variables)]
                if let Self::State::S1(output, inner_state) = state {
                    let matched = self.tuple.1.extract(inner_state);
                    let output = ({ output.0 }, { matched });
                    {
                        state = Self::State::S2(output, None);
                    }
                }
            }
            {
                #[allow(irrefutable_let_patterns, unused_variables)]
                if let Self::State::S2(output, inner_state) = state {
                    let matched = self.tuple.2.extract(inner_state);
                    let output = ({ output.0 }, { output.1 }, { matched });
                    {
                        return output;
                    }
                }
            }
            ()
        }
        unreachable!();
    }
}