Expand description
Note, for best results using typle with rust-analyzer, ensure that proc macro attributes are enabled:
rust-analyzer.procMacro.enable: true,
rust-analyzer.procMacro.attributes.enable: true§#[typle(...)]
The typle attribute macro generates code for multiple tuple lengths. This code:
use typle::typle;
struct MyStruct<T> {
t: T,
}
#[typle(Tuple for 2..=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 2 to 3 components:
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 }
}
}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 in this crate. Typle index expressions can
reduce to a single value or to a range.
/// Split off the first component of a tuple.
#[typle(Tuple for 1..=12)]
fn split_first<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,...))
}
let t = ('1', 2, 3.0);
let (first, rest) = split_first(t); // first = '1', rest = (2, 3.0)
assert_eq!(first, '1');
let (first, rest) = split_first(rest); // first = 2, rest = (3.0,)
assert_eq!(first, 2);
let (first, rest) = split_first(rest); // first = 3.0, rest = ()
assert_eq!(first, 3.0);
assert_eq!(rest, ());The default bounds for a macro range are 0..Tuple::LEN, encapsulating all
components of the tuple.
#[typle(Tuple for 0..12)]
fn append<T: Tuple, A>(t: T, a: A) -> (T<{..}>, A) {
(t[[..]], a)
}
assert_eq!(append((1, 2, 3), 4), (1, 2, 3, 4));§typle!
The typle! macro generally iterates over a range to create a new comma-separated sequence of
elements. A typle! macro with a range can only appear where a comma-separated sequence is
valid (e.g. a tuple, array, argument list, or where clause).
The typle! macro can provide an optional typle index variable for each iteration to use in the
macro expansion.
#[typle(Tuple for 0..=12)]
fn indices<T: Tuple>(t: T) -> (typle! {.. => usize}) { // (usize, usize,...)
(typle! {i in .. => i}) // (0, 1,...)
}
assert_eq!(indices(('a', Some(true), "test", 9)), (0, 1, 2, 3));The associated constant LEN provides the length of the tuple in each
generated item and can be used in typle index expressions.
#[typle(Tuple for 0..=12)]
fn reverse<T: Tuple>(t: T) -> (typle! {i in 1..=T::LEN => T<{T::LEN - i}>}) {
(typle! {i in 1..=T::LEN => t[[T::LEN - i]]})
}
assert_eq!(reverse((Some(3), "four", 5)), (5, "four", Some(3)));Each iteration can add multiple elements to the new sequence.
#[typle(Tuple for 0..=12)]
fn duplicate_components<T: Tuple>(
t: T,
) -> (typle! {i in .. => T<{i}>, T<{i}>})
where
T<_>: Clone,
{
(typle! {i in .. => t[[i]].clone(), t[[i]]})
}
assert_eq!(duplicate_components(("one", 2, 3.0)), ("one", "one", 2, 2, 3.0, 3.0));The typle! macro can be used without a range. In this case it must return
a single value but can be used outside a comma-separated sequence. Remember that typle!
with a range effectively adds a trailing comma to each element that may affect the created type.
// outside a sequence create a single `bool`
let result: bool = typle! {=> true};
// typle! with a range outside a sequence is an error
// let result = typle! {0..1 => true}; // ERROR
// inside parentheses without a range create a parenthesized `bool`
let result: (bool) = (typle! {=> true});
// inside parentheses with a 1-element range create a `bool` 1-tuple
let result: (bool,) = (typle! {0..1 => true});
// inside brackets both forms create a `bool` array
let result: [bool; 1] = [typle! {=> true}];
let result: [bool; 1] = [typle! {0..1 => true}];§Bounds
Specify bounds on the tuple components using one of the following
forms. Except for the first form, these bounds can only appear in the
where clause.
T: Tuple<C>- all components of the tuple have typeC.T<_>: Clone- all components of the tuple implement theClonetrait.T<0>: Clone- the first component of the tuple implements theClonetrait.T<{1..=2}>: Clone- the second and third components implement theClonetrait.typle!(j in .. => I<{j}>: Iterator<Item=T<{j}>>): Tuple::Bounds- the most general way to bound components, allowing typle index expressions on both sides of the colon. Note that the suffix: Tuple::Boundsis required after the macro, whereTupleis the name of the typle trait.
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! {i in .. => <S<{i}> as Mul<T<{i}>>>::Output}) // (<S<0> as Mul<T<0>>>::Output,...)
where
typle! {i in .. => S<{i}>: Mul<T<{i}>>}: Tuple::Bounds, // S<0>: Mul<T<0>>,...
{
(typle! {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)
)§Conditionals
The typle! macro accepts an if statement with an optional else clause.
If there is no else clause the macro filters out elements that do not match
the condition.
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.
#[cfg_attr(not(feature = "big-tuple"), typle(Tuple for 0..=12))]
#[cfg_attr(feature = "big-tuple", typle(Tuple for 0..=24))]
fn even_to_string<T: Tuple>(
t: T,
) -> (typle!(i in .. => if i % 2 == 0 { String } else { T<{i}> }))
where
typle!(i in .. => if i % 2 == 0 { T<{i}>: ToString }): Tuple::Bounds,
{
#[typle_attr_if(T::LEN == 0, allow(clippy::unused_unit))]
(typle!(i in .. => if i % 2 == 0 { t[[i]].to_string() } else { t[[i]] }))
}
// `Vec` does not implement `ToString` but, in an odd position, it doesn't need to
assert_eq!(even_to_string((0, vec![1], 2, 3)), (0.to_string(), vec![1], 2.to_string(), 3));An un-ranged typle! macro can be used to add an if statement where
expressions are otherwise invalid:
trait ReplaceLast {
type Last;
// Replace the "last" value with a new value, returning the old value
fn replace_last(&mut self, new: Self::Last) -> Self::Last;
}
#[typle(Tuple for 0..=3)]
impl<T: Tuple> ReplaceLast for MyStruct<T> {
type Last = typle!(=> if T::LEN == 0 { () } else { T<{T::LAST}> });
fn replace_last(
&mut self,
new: typle!(=> if T::LEN == 0 { () } else { T<{T::LAST}>}),
) -> typle!(=> if T::LEN == 0 { () } else { T<{T::LAST}> }) {
typle!(=> if T::LEN == 0 { () } else {
std::mem::replace(&mut self.t[[T::LAST]], new)
})
}
}Note, real code like the above can avoid all these ifs by using a specific impl for () and
a simplified typle impl for 1..=3.
The typle_const! macro supports const-if on a boolean typle index expression. const-if allows
conditional branches that do not compile, as long as the invalid branch is false at compile time.
The associated constant LAST is always equal to LEN - 1. LAST is not defined when
LEN == 0 and will cause a compilation error. The following code uses T::LAST but compiles
successfully for T::LEN == 0 because T::LAST only appears in an else branch that is
not compiled when T::LEN == 0.
trait HandleStuff {
type Output;
fn handle_stuff(&self, input: Input) -> Self::Output;
}
struct MultipleHandlers<T> {
handlers: T,
}
#[typle(Tuple for 0..=12)]
impl<T> HandleStuff for MultipleHandlers<T>
where
T: Tuple,
T<_>: HandleStuff,
{
type Output = (typle! {i in .. => T<{i}>::Output});
// Return a tuple of output from each handler applied to the same input.
fn handle_stuff(&self, input: Input) -> Self::Output {
if typle_const!(T::LEN == 0) {
()
} else {
(
typle! {
i in ..T::LAST => self.handlers[[i]].handle_stuff(input.clone())
},
// Avoid expensive clone for the last handler.
self.handlers[[T::LAST]].handle_stuff(input),
)
}
}
}§Iteration
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: Default + for<'a> std::ops::AddAssign<&'a C>,
{
// Return the sums of all even positions and all odd positions.
fn interleave(&self) -> [C; 2] {
let mut even_odd = [C::default(), C::default()];
for typle_index!(i) in 0..T::LEN {
even_odd[i % 2] += &self.t[[i]];
}
even_odd
}
}
let m = MyStruct::from((3, 9, 11));
assert_eq!(m.interleave(), [14, 9]);§Aggregation
The typle_fold! macro reduces a tuple to a single value.
#[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);§Selection
Indexing using [[i]] only works with tuple index expressions. To select a component from a
tuple value using a variable, use typle_index! in a match expression:
#[typle(Tuple for 1..=12)]
fn get_component<'t, C, T>(t: &'t T, i: usize) -> Option<&'t C>
where
T: Tuple<C>,
{
// `i` is a variable, `j` is a typle index variable.
match i {
j @ typle_index!(0..T::LEN) => Some(&t[[j]]),
_ => None,
}
}
let t = ('a', 'b', 'c', 'd', 'e');
assert_eq!(get_component(&t, 1), Some(&'b'));
assert_eq!(get_component(&t, 7), None);/// Trait for types that can treated as an infinitely wrapping sequence of chars.
trait WrappingString {
/// Return a 2 character substring starting at position `start`.
fn wrapping_substring_at(&self, start: usize) -> String;
}
#[typle(Tuple for 1..=12)]
impl<T: Tuple<char>> WrappingString for T {
#[typle_attr_if(T::LEN < 2, allow(unused))]
fn wrapping_substring_at(&self, start: usize) -> String {
if typle_const!(T::LEN == 1) {
[ self.0, self.0 ].into_iter().collect()
} else {
match start % T::LEN {
j @ typle_index!(0..T::LAST) => {
[ self[[j..=j + 1]] ].into_iter().collect()
}
T::LAST => {
[ self[[T::LAST]], self.0 ].into_iter().collect()
}
_ => unreachable!(),
}
}
}
}
let t = ('a', 'b', 'c', 'd', 'e');
assert_eq!(t.wrapping_substring_at(6), "bc");
assert_eq!(t.wrapping_substring_at(4), "ea");
assert_eq!(('f',).wrapping_substring_at(12), "ff");§enum
Applying typle to an enum implements the enum once for the maximum length only.
The typle_variant! macro creates multiple enum variants by looping
similarly to typle!.
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,
{
// The output of all previous components plus the state of the current component.
S = typle_variant!(curr in ..T::MAX =>
(typle! {prev in ..curr => T::<{prev}>::Output}),
Option<T<{curr}>::State>
),
}The 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.
// 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! {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 == T::LAST) {
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 (
Tuplein the examples) can only be applied to an unqualified type identifier, not to non-path types or associated types. - The
#[typle]macro 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
asyncandunsafefunctions are not supported. - Standalone functions require explicit lifetimes on references:
#[typle(Tuple for 1..=3)]
pub fn hash<'a, T: Tuple, S: Hasher>(tuple: &'a T, state: &'a mut S)
where
T<_>: Hash,
T<{T::LAST}>: ?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 can be shadowed by inner typle index variables but cannot be shadowed by standard variables:
let mut v = vec![];
for typle_index!(i) in 2..=3 {
for typle_index!(i) in 4..=5 {
let i = 1; // this `i` is ignored
v.push(i); // this `i` comes from `4..=5`
}
v.push(i); // this `i` comes from `2..=3`
}
assert_eq!(v, [4, 5, 2, 4, 5, 3]);- Due to interaction of
typlewith 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.
#[typle(Tuple for 3..=3)]
fn test_macro<T: Tuple>(t: T)
where
T<_>: PartialEq<usize> + std::fmt::Debug,
{
assert_eq!(
stringify!([T, T::LEN, typle_ty!(T), typle_expr!(T::LEN)]),
"[T, T :: LEN, (T0, T1, T2), 3]"
);
for typle_index!(i) in 0..T::LEN {
assert_eq!(typle_expr!(t[[i]]), typle_expr!(i));
}
}
test_macro((0, 1, 2));Macros§
- typle_
all - Short-circuiting check that all values are true.
- typle_
any - Short-circuiting check that any values are true.
- typle_
fold - Reduce a tuple to a single value.
- typle_
variant - Create variants in an enum.