Search code examples
rustmacros

generate integer tokens to be used to index tuples in macro


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 ?


Solution

  • 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,)*)
                }
            }
        }
    }