Call a function without knowing how many arguments it takes?

I’d like to write a function like this:

fn call<F: Fn()>(f: F) { f(); }

Where F can have any number of arguments or return values. Yes, I know this makes no sense; it’s so that I can test that my code - which links against a bunch of C functions - is linking properly. I just need to force the linker to do its thing. I don’t need to actually run this program.

Is there a way to write such a function?

I messed around a little in the playground, and this was as close as I got:

#![feature(unboxed_closures)]
#![feature(fn_traits)]

fn call<T, F: Fn<T>>(f: F, t: T) {f.call(t);}

This needs the nightly toolchain to opt into those features.

1 Like

That’s fantastic! Working off of that, I was able to get it to work for me. Thanks!

Ah, I spoke too soon. The next challenge is: get this to work with unsafe extern "C" fn :wink:

You could always use some sort of macro to generate calls to your extern "C" functions. I think your biggest issue will be that there’s no easy way to force a linker error without actually calling the function you want to link to.

I tried different variations of the following on the playground with no success.

extern "C" {
  fn this_doesnt_exist();
}

fn main() {
  if false {
    unsafe {
      this_doesnt_exist();
    }
  }
}

It seems like the best way to make sure the linker is doing its thing would be to just call into the C code. I normally try to do this when making a *-sys crate anyway because it’s essentially free examples for other people to use.

1 Like

So the issue isn’t that I can’t call the functions - I totally can - it’s just that I have upwards of 1400 functions to call, and so I really don’t want to have to write those calls manually. I’d like to generate a main like:

fn main() {
    foo(unimplemented!());
    bar(unimplemented!(), unimplemented!());
}

…but there’s no good way of analyzing the bindings (generated with bindgen) to figure out how many arguments each function takes. I tried for a few hours with a combination of rg and sed, and never managed to get it fully working. Even if I had gotten it working, it was suuuuper janky.

However, it turns out that calling isn’t actually necessary; merely accessing the pointer value will cause linking:

fn main() {
    println!("{:?}", foo as *const ());
    println!("{:?}", bar as *const ());
}

So thankfully, the question is moot!

4 Likes

Oh wow, that’s a lot! Have you considered writing a python script to search for any extern "C" fn and generate/compile your linker test on the fly?

That’s essentially what the sed/rg soup I mentioned attempted to do, but it was a nightmare, and I suspect doing it in Python wouldn’t have been much better. Luckily, the println! approach does the trick, and only requires figuring out the function names, not how many arguments each function takes.