How to iterate over a tuple of types implementing the same trait?

Hello.

I have a template type A with a template number P (see code below).
In my real use-case, the type A is from a library and is very similar to the type in the example code. Even though not an empty struct, the real type struct always have the same size whatever the template variable is.

The type A implements A trait B for any template number P.

There is a struct C that contains a tuple of multiple types A with different template variable P (in the example it is 1 and 2).

My question is: Can I iterate over that tuple and call the method of the trait B?

First way: Duplicate code for all the tuple elements:

c.tuple.N.hello()

That could be problematic if the tuple size gets large enough, and also it is just code duplication.
In my real use-case, I have larger tuple size.

Second way: I thought I could iterate over the tuple in a straightforward way with a for loop. However, the code doesn't compile and returns an error:

error[E0277]: `(A<1>, A<2>)` is not an iterator

Third way: the best I could find for now.
Store references to each element of the tuple casted to B in a static array.

c.refs = Some([&c.tuple.0 as &dyn B, &c.tuple.1]);

Then use for loop to iterate over the array.

for element in c.refs.unwrap() {
        element.hello()
    }

It works. However, seems like it is somewhat redundant to create self-references and just wrong to store references to objects to which I already have access to in a tuple.
Also, I couldn't find a way to store the references in a static array without Option type.

So, is there any better way to iterate over the elements of same type A with different template variable P implementing same trait B?

The example code:

struct A<const P: u32>;

trait B {
    fn hello(&self);
}

impl<const P: u32> B for A<P> {
    fn hello(&self) {
        println!("Hello: {}", P);
    }
}

struct C<'a> {
    tuple: (
        A::<1>,
        A::<2>
    ),
    refs: Option<[&'a dyn B; 2]>,
}

fn main() {
    let mut c = C {
        tuple: (
            A::<1> {},
            A::<2> {},
        ),
        refs: None
    };
    
    // First way
    c.tuple.0.hello();
    c.tuple.1.hello();

    // Second way?
    // for element in c.tuple {
    //     element.hello()
    // }

    // Third way
    c.refs = Some([&c.tuple.0 as &dyn B, &c.tuple.1]);
    
    for element in c.refs.unwrap() {
        element.hello()
    }
}

Here’s a fun approach I could come up with :slight_smile:

pub trait Tuple<Callback> {
    fn for_each(&self, cb: Callback);
}

pub trait CallbackFor<T> {
    fn call(&mut self, e: &T);
}

macro_rules! impl_tuple_trait {
    () => {};
    ($_t:ty, $($T:ident),* $(,)?) => {
        impl<Callback, $($T),*> Tuple<Callback> for ($($T,)*)
        where $(
            Callback: CallbackFor<$T>,
        )* {
            #[allow(unused_mut, unused_variables)]
            fn for_each(&self, mut cb: Callback) {
                #[allow(non_snake_case)]
                let ($($T,)*) = self;
                $(
                    cb.call($T);
                )*
            }
        }
        impl_tuple_trait!($($T,)*);
    };
}
impl_tuple_trait!((), T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);

macro_rules! dyn_callback {
    ($([[$($generics:tt)*] $(where $($where_:tt)*)?])? $Bound:path) => {{
        $(trait __Tr<$($generics)*,> {})?
        struct __C<$($($generics)*,)? __F: FnMut(&dyn $Bound)>(::std::marker::PhantomData<($(Box<dyn __Tr<$($generics)*>>,)?)>, __F);
        impl<'a, $($($generics)*,)? __T: $Bound, __F: FnMut(&dyn $Bound)> $crate::CallbackFor<__T>
        for __C<$($($generics)*,)? __F>
            $($(where $($where_)*)?)?
        {
            fn call(&mut self, e: &__T) {
                (self.1)(e as &dyn $Bound)
            }
        }
        fn __c<$($($generics)*,)? __F: FnMut(&dyn $Bound)>(x: __F) ->
        __C<$($($generics)*,)? __F> {
            __C(std::marker::PhantomData, x)
        }
        __c::<$($($generics)*,)? _>
    }};
}

struct A<const P: u32>;

trait B {
    fn hello(&self);
}

impl<const P: u32> B for A<P> {
    fn hello(&self) {
        println!("Hello: {}", P);
    }
}

struct C {
    tuple: (
        A::<1>,
        A::<2>
    ),
}

fn main() {
    let c = C {
        tuple: (
            A::<1> {},
            A::<2> {},
        ),
    };

    c.tuple.for_each(dyn_callback!(B)(|element|
        element.hello()
    ));
}

// quick demo of the optional feature of incorporating some generic parameters and bounds

fn _demo2<T>(call: impl Fn(&T), x1: impl AsRef<T>, x2: impl AsRef<T>) {
    (x1, x2).for_each(dyn_callback!([[T]] AsRef<T>)(|element| {
        call(element.as_ref());
    }));
}
fn _demo3<T: std::fmt::Display>(call: impl Fn(&T), x1: impl AsRef<T>, x2: impl AsRef<T>) {
    (x1, x2).for_each(dyn_callback!([[T] where T: std::fmt::Display] AsRef<T>)(|element| {
        println!("{}", element.as_ref());
        call(element.as_ref());
    }));
}
1 Like

The ability to do this is a standard known desired feature under the envelope of reflection functionality.

The standard way to do this kind of thing is to implement a trait for every tuple size up to some limit (typically 16 or 32), e.g. as steffahn did. You'll sometimes see a helper macro when this is done often, e.g.

macro_rules! for_every_tuple { ($m:path) => {
    $m!((T0,));
    $m!((T0, T1,));
    $m!((T0, T1, T2,));
    // and so on, to desired max tuple size
    // make it a proc macro for more control
    // include unit empty tuple if desired
}}

macro_rules! impl_tuple_callback { (($($T:ident,)*)) => {
    impl<Callback, $($T),*> Tuple<Callback> for ($($T,)*)
    where $(
        Callback: CallbackFor<$T>,
   )* {
        #[allow(unused_mut, unused_variables)]
        fn for_each(&self, mut cb: Callback) {
            #[allow(non_snake_case)]
            let ($($T,)*) = self;
            $(
                cb.call($T);
            )*
        }
    }
}}

for_every_tuple!(impl_tuple_callback);

This isn't that useful when you have only one tuple projecting trait to implement, but becomes very useful when you have a larger number of them.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.