Search code examples
rustrust-proc-macros

How do I pass arguments from a generated function to another function in a procedural macro?


I'm trying to create a macro that generates two functions from a function signature. As the signature of the functions is dependent from what is passed to the macro, the number of function arguments is varying.

The functions I want to create are (i) an internal function, that actually does something (which I was able to generate as I wanted) and (ii) a public function that should wrap around the internal function. The function signatures are the same regarding the parameters. Therefore, it should be easy to pass all the arguments that were passed to the public function down into the internal function.

The following snippet shows the simplified macro generation for the public function. How can I achieve that the arguments are passed down correctly? (#params_from_public_function)

let public_function_ident = ...;
let internal_function_ident = ...;

// ast_function_stub-Type: ForeignItemFn
let params_from_public_function: Punctuated<FnArg, Comma> = ast_function_stub.sig.inputs.clone();


quote! {
  pub fn #public_name(#params_from_public_function) {
    #internal_function_ident(#params_from_public_function);
  }
}

Here are two examples, how the resulting macro should work:

generate_both_functions!(fn foo(someParam: &str) -> u8;);

// What the macro should generate:

fn _foo(someParam: &str) -> u8 { // Internal function
  // Some internal stuff happens here
}

pub fn foo(someParam: &str) { // Public function
  _foo(someParam);
}

// Second example (having two instead of one argument and different types)

generate_both_functions!(fn bar(param1: i32, param2: String) -> String;);

// What the macro should generate
fn _bar(param1: i32, param2: String) -> String {
  // Internal stuff again
}

fn bar(param1: i32, param2: String) {
  _bar(param1, param2);
}

Just to clarify: I was able to correctly parse the macro's input (generate_both_functions!) and extract the function argument metadata from the AST. However, I am not able to pass the actual inputs dynamically to the internal function.


Solution

  • This is not possible in full generality. Consider the following function signature, which makes use of destructuring in argument position to extract the fields of its first parameter:

    fn bar(Foo { a, b, .. }: Foo)
    

    Since the Foo has been destructured, there is no way for the macro to generate code which passes it to another function with the exact same signature. You might try to reconstruct the Foo and call _bar like _bar(Foo { a, b }), but due to the .. some of the fields have been permanently lost and it cannot be reconstructed.

    The only remaining option here is to extract the set of bindings from the pattern (which is not trivial), and generate a function that takes each of those bindings as separate parameters. But this is also impossible. We'd have to generate a signature like fn _bar(a: A, b: B), where A and B are the types of a and b. But the macro cannot see the declaration of the type Foo, and so it has no way to determine what these types are.

    Alternatives

    • It is possible to support the common case where each argument has the form $ident: $type. All you need to do in this case is to check that the pat field on each syn::FnArg is a Pat::Ident and grab the name of the argument. Then you can generate a matching signature for _bar and pass each argument in.

    • If no external code needs to call _bar, you can make it a by-move closure:

      fn bar(/* any signature you like */) {
          let _bar = move || { /* body goes here, using any arguments from bar */ };
          _bar();
      }
      

      This should inherit all the bindings of the outer function seamlessly, but the outer function will no longer be able to use them after, which may be a dealbreaker.