I'm trying to write a macro that can generelize this kind of implementation (convert a tuple to a type-level list) :
impl<A, B, C> TupleToList for (A, B, C) {
type List = Cons<A, Cons<B, Cons<C, Nil>>>;
fn to_list(self) -> Self::List {
Cons { head: self.0, tail:
Cons { head: self.1, tail:
Cons { head: self.2, tail:
Nil
}
}
}
}
}
(with the following structs / traits declaration)
pub trait List {}
pub struct Nil;
pub struct Cons<Head, Tail: List> {
pub head: Head,
pub tail: Tail,
}
pub trait TupleToList {
type List;
fn to_list(self) -> Self::List;
}
The idea would be to have such a macro that can implement this trait for any tuple (T1, T2, T3, ... TN)
.
So far, I've managed to do most of the work, with one missing piece:
/// Recursively build the list type for list / tuple conversions.
macro_rules! inner_tuple_to_list {
() => {Nil};
($elem:ty, $($rest:tt)*) => {
Cons<$elem, inner_tuple_to_list!($($rest)*)>
};
}
macro_rules! inner_to_list_func {
($self:ident,) => { Nil };
($self:ident, $elem:ty, $($rest:tt)*) => {
Cons { head: $self.0, tail: inner_to_list_func!($self, $($rest)*) }
}; // ^------ MISSING DETAIL
}
/// Implements the TupleToList and ListToTuple traits for any tuples.
macro_rules! tuple_to_list {
($($elems:tt)*) => {
impl <$($elems)*> TupleToList for ($($elems)*) {
type List = inner_tuple_to_list!($($elems)*);
fn to_list(self) -> Self::List {
inner_to_list_func!(self, $($elems)*)
}
}
}
}
tuple_to_list!(T1, T2,);
I'm having a compilation error, where it is expecting type T2
but get T1
. Obviously, because I hard coded self.0
in my macro impl (where I added the comment missing detail).
What I would like is to keep a counter, use it in place $self.$counter
and increase it at each recursion, to incrementally put self.0
, self.1
, self.2
, etc.
The techniques I know to count up in a macro (described here) produce tokens that compile down to the number, but to index my tuple I actually need the integer token.
Are there any techniques to achieve this ? maybe some other macros or libraries ?
You cannot create such increasing integer without a proc macro. You can use a proc-macro crate for that, however, such as seq-macro
.
However, you don't need libraries for that. You can employ a different trick: tuples support destructuring.
So instead of referring to self.0
, self.1
. etc., you can at the beginning of the function do let (a, b, c, ...) = self;
then use a
, b
etc..
Of course, generating those identifiers is also impossible without a proc macro, but we don't need to generate them - we already have identifiers of exactly that length - the type parameter names. We can just reuse them.
We need to slice the warning of non-snake-case variables, but it works.
/// Recursively build the list type for list / tuple conversions.
macro_rules! inner_tuple_to_list {
() => { Nil };
($elem:ty, $($rest:tt)*) => {
Cons<$elem, inner_tuple_to_list!($($rest)*)>
};
}
macro_rules! inner_to_list_func {
() => { Nil };
($elem:ident, $($rest:tt)*) => {
Cons { head: $elem, tail: inner_to_list_func!($($rest)*) }
};
}
/// Implements the TupleToList and ListToTuple traits for any tuples.
macro_rules! tuple_to_list {
($($elem:tt,)*) => {
impl<$($elem,)*> TupleToList for ( $($elem,)* ) {
type List = inner_tuple_to_list!($($elem,)*);
fn to_list(self) -> Self::List {
#[allow(non_snake_case)]
let ( $($elem,)* ) = self;
inner_to_list_func!($($elem,)*)
}
}
}
}