Recursive macros

Is there a way to do this in Rust? Are recursive macros the answer? (Depth can be bounded if necessary.)

scaled_add(x,y,c) (or something like x.scaled_add(y,c) ) should return x + c*y if x,y are both of some type called F (which is a field). If instead x and y are Vec<F>, then do this elementwise and return a Vec<F>. If they are Vec<Vec<F>>, then return another Vec<Vec<F>>, etc.

So for this one could write:

pub trait ScaledAdd {
    fn scaled_add(self, other: &Self, c: &F) -> Self;
}

impl ScaledAdd for F {
    fn scaled_add(self, other: &Self, c: &F) -> Self {
        self + c * other
    }
}

macro_rules! impl_scaled_add_for_vec {
    ($t:ty) => {
        impl ScaledAdd for Vec<$t> {
            fn scaled_add(self, other: &Self, c: &F) -> Self {
                self.into_iter()
                    .zip(other.into_iter())
                    .map(|(a,b)| a.scaled_add(b, c))
                    .collect()
            }
        }
    }
}

But now what if x and y are of type (F, Vec<F>)? Or (F, &Vec<F>)? Is there a simple way to handle this?

You don't need (and shouldn't use) macros to do this; this type of recursion can be expressed as generic trait impls.

impl ScaledAdd for F {...}
impl<T: ScaledAdd> ScaledAdd for &T {...}
impl<T: ScaledAdd> ScaledAdd for Vec<T> {...}
impl<T: ScaledAdd, U: ScaledAdd> ScaledAdd for (T, U) {...}

Oh implementing it for (T,U) suffices for tuples of arbitrary length because a tuple is defined recursively in terms of pairs?

Sorry, I didn't entirely follow the structure of your question. When I said that generic impls handle recursion, I meant recursion of the type Vec<Vec<...>>. You do not need, and therefore should not use, a macro for such cases; I am specifically recommending that you replace impl_scaled_add_for_vec with a generic impl.

For tuples of different lengths (which you did not mention in your original question), there is not any recursion involved, and you do have to use a macro to avoid repetition for different lengths. Someday Rust may have “variadic generics” to solve this, but it doesn’t today.

A straightforward way to write such a macro is:

macro_rules! impl_scaled_add_for_tuple {
    ($( $t:ident )*) => {
        impl<$( $t: ScaledAdd, )*> ScaledAdd for ($( $t, )*) {
            fn scaled_add(self, other: &Self, c: &F) -> Self {
                ...
            }
        }
    }
}
impl_scaled_add_for_tuple!(A);
impl_scaled_add_for_tuple!(A B);
impl_scaled_add_for_tuple!(A B C);
impl_scaled_add_for_tuple!(A B C D);
impl_scaled_add_for_tuple!(A B C D E);
// ...

It’s possible to avoid the duplication in the macro calls, but I don't it's worth the added complexity.

1 Like

Yes thanks that was very helpful.

Could you perhaps show me how you would finish the macro you just sketched? I can't get it to work without supplying extra variable names...

You're right, it needs additional input (or some proc-macro helper, perhaps). I'd do it this way:

macro_rules! impl_scaled_add_for_tuple {
    ([$( $t:ident )*], [$( $i:tt )*]) => {
        impl<$( $t: ScaledAdd, )*> ScaledAdd for ($( $t, )*) {
            fn scaled_add(self, other: &Self, c: &F) -> Self {
                ($(
                    self.$i.scaled_add(&other.$i, c),
                )*)
            }
        }
    }
}
impl_scaled_add_for_tuple!([A], [0]);
impl_scaled_add_for_tuple!([A B], [0 1]);
impl_scaled_add_for_tuple!([A B C], [0 1 2]);

Playground

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.