Crate typle

source ·
Expand description

The typle macro generates code for multiple tuple lengths. This code

use typle::typle;

struct MyStruct<T> {
    t: T,
}

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

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, individual components of a tuple can be selected using <{i}> for types and [[i]] for values. The value i must be a typle index expression, an expression that only uses literal usize values or typle indexes created by one of several macros.

The typle_for! macro creates a new tuple type or value. Inside the macro the typle index can provide access to each component of an existing tuple type or value.

// Split off the first component
#[typle(Tuple for 1..=12)]
fn split<T: Tuple>(
    t: T  // t: (T0, T1, T2,...)
) -> (T<0>, typle_for!(i in 1.. => T<{i}>))   // (T0, (T1, T2,...))
{
    (t[[0]], typle_for!(i in 1.. => t[[i]]))  // (t.0, (t.1, t.2,...))
}

assert_eq!(split(('1', 2, 3.0)), ('1', (2, 3.0)));
assert_eq!(split((2, 3.0)), (2, (3.0,)));
assert_eq!(split((3.0,)), (3.0, ()));

Specify constraints on the tuple components using one of the following forms. Except for the first form, these constraints can only appear in the where clause.

  • T: Tuple<C> - each component of the tuple has type C
  • T<_>: Copy - each component of the tuple implements Copy
  • T<0>: Copy - the first component of the tuple implements Copy
  • T<{1..=2}>: Copy - the second and third components implement Copy
  • typle_bound! - the most general way to bound components, allowing the typle index to be used in the trait bounds, as shown below:
use std::{ops::Mul, time::Duration};

// Multiply the components of two tuples
#[typle(Tuple for 0..=12)]
fn multiply<S: Tuple, T: Tuple>(
    s: S,  // s: (S0,...)
    t: T,  // t: (T0,...)
) -> typle_for!(i in .. => <S<{i}> as Mul<T<{i}>>>::Output)  // (<S0 as Mul<T0>>::Output,...)
where
    typle_bound!(i in .. => S<{i}>): Mul<T<{i}>>,  // S0: Mul<T0>,...
{
    typle_for!(i in .. => s[[i]] * t[[i]])  // (s.0 * t.0,...)
}

assert_eq!(
    multiply((Duration::from_secs(5), 2), (4, 3)),
    (Duration::from_secs(20), 6)
)

The associated constant LEN provides the length of the tuple in each generated item. It can be used as a typle index.

Use the typle_index! macro in a for loop to iterate over a range bounded by typle index expressions.

#[typle(Tuple for 1..=3)]
impl<T, C> MyStruct<T>
where
    T: Tuple<C>,
    C: for<'a> std::ops::AddAssign<&'a C> + Default,
{
    // Return the sums of all odd positions and all even positions.
    fn interleave(&self) -> [C; 2] {
        let mut odd_even = [C::default(), C::default()];
        for typle_index!(i) in 0..T::LEN {
            odd_even[i % 2] += &self.t[[i]];
        }
        odd_even
    }
}

let m = MyStruct::from((3, 9, 11));
assert_eq!(m.interleave(), [14, 9]);

The next example is simplified from code in the hefty crate and demonstrates the use of typle with enums.

The typle_variant! macro creates multiple enum variants by looping similarly to typle_for!.

Typled enums and structs require a separate identifier for each tuple length. The typle macro adds the tuple length to their original name. For example enum TupleSequenceState<T> expands to enum TupleSequenceState3<T0, T1, T2> for 3-tuples. When referring to these types from other typled items, use TupleSequenceState<T<{ .. }>>.

Use the typle_ident! macro to concatenate a number to an identifier. For example S::<typle_ident!(3)> becomes the identifer S3.

The typle_attr_if attribute allows conditional inclusion of attributes. It works similarly to cfg_attr except that the first argument is a boolean typle index expression.

The typle_const! macro supports const-if on a boolean typle index expression. 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 identifier S::<typle_ident!(T::LEN)> (S3 for 3-tuples) is not defined.

use typle::typle;

#[typle(Tuple for 1..=12)]
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 {
            #[typle_attr_if(T::LEN == 1, allow(unused_mut))]
            let mut state = state.unwrap_or(Self::State::S::<typle_ident!(0)>((), None));
            for typle_index!(i) in 0..T::LEN {
                // For LEN = 1 there is only one variant (S0) so `let` is irrefutable
                #[typle_attr_if(T::LEN == 1, allow(irrefutable_let_patterns, unused_variables))]
                if let Self::State::S::<typle_ident!(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_ident!(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 {
        let mut state = state.unwrap_or(Self::State::S0((), None));
        {
            {
                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);
                    }
                }
            }
            {
                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);
                    }
                }
            }
            {
                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!();
    }
}

§Limitations

  • The typle trait (Tuple in the examples) cannot be combined with other constraints. To support ?Sized tuples constrain the last component using T<{T::LEN - 1}>: ?Sized.
  • Standalone async and unsafe functions are not supported.
  • Standalone functions require explicit lifetimes on references
#[typle(Tuple for 1..=3)]
pub fn hash<'a, T, S: Hasher>(tuple: &'a T, state: &'a mut S)
where
    T: Tuple,
    T<_>: Hash,
    T<{T::LEN - 1}>: ?Sized,
{
    for typle_index!(i) in 0..T::LEN {
        tuple[[i]].hash(state);
    }
}
  • Shadowing of typle indexes is not supported. For example, in:
for typle_index!(i) in 2..=3 {
    let i = 1;
    func(i)
}

func will be called with 2 and 3, never with 1. The same is true for other places where typle indexes are introduced. For example in a typle_for! macro.

  • A continue referencing a label on a for loop using typle_index! works but displays an unsuppressible warning during compilation.
// warning: label name `'cont` shadows a label name that is already in scope
'cont: for typle_index!(i) in 2..=3 {
    loop {
        if typle_const!(i == 2) {
            continue 'cont;
        }
        break;
    }
}

Macros§