0

I am writing a library in Rust where I need to take functions with arbitrary function signatures and store them in a Vector. This task is fairly challenging since Rust is not only strictly typed, but also needs to know sizes at compile time.

I was already at the point to accept, that I have to solve that differently (e.g. by serializing the function's parameters and returns to JSON via macro so that the signature is always (String) -> String. Then I realized that in Wasmtime it is possible to provide arbitrary import functions. Therefore, I tried to research how it is done there and stumbled across a technique I don't understand:

I refer to that file from the Wasmtime-Repository

Wasmtime has a trait (IntoFunc) which is implemented by the impl_into_func macro. That macro however is not applied directly on a certain type, but via another macro (for_each_function_signature). The latter is implemented like this:

macro_rules! for_each_function_signature {
    ($mac:ident) => {
        $mac!(0);
        $mac!(1 A1);
        $mac!(2 A1 A2);
        $mac!(3 A1 A2 A3);
        $mac!(4 A1 A2 A3 A4);
        $mac!(5 A1 A2 A3 A4 A5);
        $mac!(6 A1 A2 A3 A4 A5 A6);
        $mac!(7 A1 A2 A3 A4 A5 A6 A7);
        $mac!(8 A1 A2 A3 A4 A5 A6 A7 A8);
        $mac!(9 A1 A2 A3 A4 A5 A6 A7 A8 A9);
        $mac!(10 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10);
        $mac!(11 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11);
        $mac!(12 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12);
        $mac!(13 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13);
        $mac!(14 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14);
        $mac!(15 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15);
        $mac!(16 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15 A16);
    };
}

And the former like this:

macro_rules! impl_into_func {
    ($num:tt $($args:ident)*) => {
        impl<T, F, $($args,)* R> IntoFunc<T, ($($args,)*), R> for F
        where
            F: Fn($($args),*) -> R + Send + Sync + 'static,
            $($args: WasmTy,)*
            R: WasmRet,
        {
            // ...
        }
    }
}

And finally the IntoFunc trait is defined as:

trait IntoFunc<T, Params, Results> {
    // ...
}

What I don't understand is: The number of generic arguments being passed to the implementation of IntoFunc is dependent on the for_each_function_signature macro. Taking the last appliance of that macro, the respective IntoFunc-implementation should have 16 + 3 (T, F, R) generic parameters due to impl<T, F, $($args,)* R>. How is this possible / valid since the definition of the trait only has three generic parameters?

If someone who knows how this works (or even a Wasmtime maintainer) could explain me how it works, I would really appreciate since it looks like a valuable and powerful technique I could apply in my own use-case.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • 2
    If you look closely at the `impl` block generated by `impl_into_func`, you will see that the parameters are put in a tuple, which makes up a single type parameter: `($($args,)*)` (notice the parenthesis). – Jmb Oct 17 '22 at 15:07
  • @Jmb I see. I have to admit this is truly brilliant – Konrad Koschel Oct 18 '22 at 06:03

0 Answers0