No, tuple indexing is not possible within a declarative macro. The expression $idx + 1
will produce distinct tokens, like 0
, +
, and 1
, and there is no way to evaluate that to a single literal token.
Procedural macros, however, can easily handle this task. You can use the typle
crate to generate code that works with tuples of any length:
#[typle(Tuple for 0..=12)]
impl Packer for Iproto
where
T: Tuple,
typle_bound!(i in .. => T<{i}>): Pack<T<{i}>>,
{
fn pack(self, data: T) -> Vec<u8> {
let mut out = Vec::new();
for typle_index!(i) in 0..T::LEN {
out.append(&mut T::<{i}>::pack(data[[i]]));
}
out
}
}
This generates code equivalent to the following for 0..12 components:
impl Packer<(T0, T1)> for Iproto
where
T0: Pack,
T1: Pack,
{
fn pack(self, data: (T0, T1)) -> Vec<u8> {
let mut out = Vec::new();
out.append(&mut <T0>::pack(data.0));
out.append(&mut <T1>::pack(data.1));
out
}
}
Another approach with declarative macros involves using destructuring, which allows you to access tuple elements by their names instead of indices:
macro_rules! gen_packer {
($($T:ident),+) => {
impl<$($T,)+> Packer<($($T,)+)> for Iproto
where $(Iproto: Packer<$T>,)+
{
fn pack(&mut self, data: ($($T,)+)) -> Vec<u8> {
#[allow(non_snake_case)]
let ( $($T,)+ ) = data;
let mut out = vec![];
$(
out.append(&mut self.pack($T));
)*
out
}
}
}
}
pub struct Iproto { }
pub trait Packer<T> {
fn pack(&mut self, arg: T) -> Vec<u8>;
}
gen_packer!(A);
gen_packer!(A, B);
// ...
Finally, you can use a recursive macro to generate the code for tuples of any length:
macro_rules! gen_packer {
() => {
impl Packer<()> for Iproto {
fn pack(&mut self, _data: ()) -> Vec<u8> {
vec![]
}
}
};
($first:ident $(, $rest:ident)*) => {
impl<$first $(, $rest)*> Packer<($first, $($rest,)*)> for Iproto
where
Iproto: Packer<$first>,
$(Iproto: Packer<$rest>,)*
{
fn pack(&mut self, data: ($first, $($rest,)*)) -> Vec<u8> {
#[allow(non_snake_case)]
let ( $first, $($rest,)* ) = data;
let mut out = vec![];
out.append(&mut self.pack($first));
$(
out.append(&mut self.pack($rest));
)*
out
}
}
gen_packer!($($rest),*);
};
}
pub struct Iproto { }
pub trait Packer<T> {
fn pack(&mut self, arg: T) -> Vec<u8>;
}
gen_packer!(A, B, C, D, E, F, G, H, I, J, K);