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.