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 typeC
T<_>: Copy
- each component of the tuple implementsCopy
T<0>: Copy
- the first component of the tuple implementsCopy
T<{1..=2}>: Copy
- the second and third components implementCopy
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
andunsafe
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 thetyple_ty!
macro expands types and thetyple_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.