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 index variables created by one of several macros, and reduces to a single value or a range.

// Split off the first component
#[typle(Tuple for 1..=12)]
fn split<T: Tuple>(
    t: T  // t: (T<0>, T<1>, T<2>,...)
) -> (T<0>, (T<{1..}>,))  // (T<0>, (T<1>, T<2>,...))
{
    (t[[0]], (t[[1..]],))  // (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, ()));

The typle_for! macro creates a new type or expression. Inside the macro the typle index variable provides access to each component of an existing tuple type or expression.

The associated constant LEN provides the length of the tuple in each generated item. This value can be used in typle index expressions.

#[typle(Tuple for 0..=12)]
fn reverse<T: Tuple>(t: T) -> typle_for!(i in 1..=T::LEN => T<{T::LEN - i}>) {
    typle_for!(i in 1..=T::LEN => t[[T::LEN - i]])
}

assert_eq!(reverse((Some(3), "four", 5)), (5, "four", Some(3)));

The typle_fold! macro reduces a tuple to a single value.

The default bounds for a macro range are 0..Tuple::LEN, that is, for all components of the tuple.

#[typle(Tuple for 0..=12)]
fn sum<T: Tuple<u32>>(t: T) -> u32 {
    typle_fold!(0; i in .. => |total| total + t[[i]])
}

assert_eq!(sum(()), 0);
assert_eq!(sum((1, 4, 9, 16)), 30);

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 arbitrary expressions using the typle index variable on both sides of the colon, 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: (S<0>,...)
    t: T,  // t: (T<0>,...)
) -> typle_for!(i in .. => <S<{i}> as Mul<T<{i}>>>::Output)  // (<S<0> as Mul<T<0>>>::Output,...)
where
    typle_bound!(i in .. => S<{i}>): Mul<T<{i}>>,  // S<0>: Mul<T<0>>,...
{
    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)
)

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]);

Applying typle to an enum implements the enum for the maximum length, allowing use of typle index variables to define the variants.

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

pub trait Extract {
    type State;
    type Output;

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

#[typle(Tuple for 1..=4)]
pub enum TupleSequenceState<T>
where
    T: Tuple,
    T<_>: Extract,
{
    S = typle_variant!(i in ..T::MAX =>
        typle_for!(j in ..i => T::<{j}>::Output), Option<T<{i}>::State>
    ),
}

The single generated implementation:

// enum implemented only for maximum size
pub enum TupleSequenceState<T0, T1, T2, T3>
where
    T0: Extract,
    T1: Extract,
    T2: Extract,
    T3: Extract,
{
    S0((), Option<<T0>::State>),
    S1((<T0>::Output,), Option<<T1>::State>),
    S2((<T0>::Output, <T1>::Output), Option<<T2>::State>),
    S3((<T0>::Output, <T1>::Output, <T2>::Output), Option<<T3>::State>),
}

Other typle implementations can refer to this enum using TupleSequenceState<T<{ ..T::MAX }>>. This will fill in unused type parameters with the never type provided for the typle macro. The default type is ! but this is not available in stable Rust. std::convert::Infallible is an uninhabited type that is available in stable Rust, but any type is permissible.

The typle_ident! macro concatenates a number to an identifier. For example S::<typle_ident!(3)> becomes the identifier S3. This is mainly used to refer to enum variants.

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 for T::LEN == 4 even though the variant TupleSequenceState::S4 does not exist because the branch that refers to it is false when (i + 1 == T::LEN)

// Relevant traits may need to be implemented for the never type.
impl Extract for std::convert::Infallible {
    type State = std::convert::Infallible;
    type Output = ();

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

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

#[typle(Tuple for 1..=4, never=std::convert::Infallible)]
impl<T> Extract for TupleSequence<T>
where
    T: Tuple,
    T<_>: Extract,
{
    // The state contains the output from all previous components and
    // the state of the current component.
    type State = TupleSequenceState<T<{ ..T::MAX }>>;
    // The final output is a tuple of outputs from all components.
    type Output = typle_for!(i in .. => <T<{i}> as Extract>::Output);

    fn extract(&self, state: Option<Self::State>) -> Self::Output {
        // When LEN == 1 the code never changes `state`
        #[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 {
            // When i == 0, the `output` state variable does not get used
            #[typle_attr_if(i == 0, allow(unused_variables))]
            if let Self::State::S::<typle_ident!(i)>(output, inner_state) = state {
                let matched = self.tuple[[i]].extract(inner_state);
                let output = (output[[..i]], 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:

impl<T0, T1, T2> Extract for TupleSequence<(T0, T1, T2)>
where
    T0: Extract,
    T1: Extract,
    T2: Extract,
{
    // reference to enum uses `never` type for unused type parameters.
    type State = TupleSequenceState<T0, T1, T2, std::convert::Infallible>;
    type Output = (
        <T0 as Extract>::Output,
        <T1 as Extract>::Output,
        <T2 as Extract>::Output,
    );
    fn extract(&self, state: Option<Self::State>) -> Self::Output {
        let mut state = state.unwrap_or(Self::State::S0((), None));
        loop {
            {
                #[allow(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);
                    }
                }
            }
            {
                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;
                    }
                }
            }
            break;
        }
        unreachable!();
    }
}

§Limitations

  • The typle trait bound (Tuple in the examples) can only be applied to an unqualified type identifier, not to non-path types or associated types.
  • typle does not work when the tuple types are only associated types because associated types cannot distinguish implementations. See this file for workarounds.
// ERROR: conflicting implementations of trait `TryUnzip`
#[typle(Tuple for 2..=3)]
impl<I, T, E> TryUnzip for I
where
    I: Iterator<Item = Result<T, E>>, // T only appears as associated type of Self
    T: Tuple,
{}
  • 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);
    }
}

Explicit lifetimes are also required for methods bound by a typle trait inside an impl that is not bound by a typle trait:

#[typle(Tuple for 1..=3)]
impl A {
    fn identity<'a, T: Tuple>(&'a self, t: &'a T) -> &'a T {
        t
    }
}
  • Typle index variables cannot be shadowed:
let mut v = vec![];
for typle_index!(i) in 2..=3 {
    let i = 1;
    v.push(i);
}
assert_eq!(v, [2, 3]);
  • Due to interaction of typle with other macros, passing some types and expressions to a macro may produce unexpected results. To help work around this, inside a macro invocation the typle_ty! macro expands types and the typle_expr! macro expands expressions.
assert_eq!(
    stringify!([T, typle_ty!(T), T::LEN, typle_expr!(T::LEN)]),
    "[T, (T0, T1, T2), T :: LEN, 3]"
);

Macros§

typle_all
Short-circuiting check that all values are true.
typle_any
Short-circuiting check that any values are true.
typle_args
Insert tuple components into a sequence.
typle_fold
Reduce a tuple to a single value.
typle_for
Create a tuple or array.
typle_get
Select an element from a tuple.
typle_variant
Create variants in an enum.