Cast function item to function pointer

I'm writing a macro. I need to do something along these lines:

use core::ffi::c_void;

fn my_fn(_: i32) {}

fn foo(ptr: *mut c_void) {
    let fn_ptr = if true {
        unsafe { core::mem::transmute(ptr) }
    } else {
        my_fn
    };
    
    (fn_ptr)(17);
}

Basically, the idea is that the void pointer is a function pointer to a function of the same signature as my_fn. I'm using type inference of the if/else branch to tell the transmute which function pointer type to transmute ptr to.

However, this code fails:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
 --> src/lib.rs:7:18
  |
7 |         unsafe { core::mem::transmute(ptr) }
  |                  ^^^^^^^^^^^^^^^^^^^^
  |
  = note: source type: `*mut c_void` (64 bits)
  = note: target type: `fn(i32) {my_fn}` (0 bits)

The problem is that my_fn is a zero-sized function item rather than a function pointer. How can I coerce my_fn to a full function pointer so that I can let type inference do its thing?

I got it to compile (not tested) by using this:

fn foo(ptr: *mut c_void) {
    let fn_ptr = if true {
        unsafe { core::mem::transmute(ptr) }
    } else {
        my_fn as fn(_) -> _
    };
    
    (fn_ptr)(17);
}

On the basis that if you're going to call the result, then you have to know the number of arguments, and that's enough too trick type inference into doing what you want.

But if you need to solve the general case... hell if I know. I do know I've wanted exactly this in the past, but Rust always disappoints me with its lack of decltype/typeof and the like :stuck_out_tongue:

Addendum: I recall trying to use FnOnce<_> once to do the same thing (since that wouldn't care how many parameters there are), but the compiler didn't like that at all. Plus, y'know, unstable feature.

Thanks for the suggestion. For now I'm using

macro_rules! to_underscore {
    ($arg:expr) => {
        _
    };
}

with a type annotation of unsafe extern "C" fn($(to_underscore!($args)),*) -> _. It's kind of gross, though.

Minor note: if you aren't matching an existing C signature which uses void *ptr, it can be marginally better to use a fn() pointer to carry opaque function pointers, despite being incorrectly callable, because this works better for Harvard architectures like wasm which have different representations for function and data pointers. A type like sptr::OpaqueFnPtr boxes this up.

Depending on the exact target calling convention, you might get away with transmuting to a function pointer type of unsafe extern "C" fn((), ...) -> _. Rust requires at least one non-variadic argument (C23 removes this requirement from C), but we fool this by using an argument of () which is invisible to the ABI.

If we don't want to commit ABI crimes, since Rust doesn't provide a way to say "function pointer but let inference handle arity" the best bet available is to create the appropriate fn(_) -> _ type, unfortunately.

Eventually it should be possible to write fn($( ${ignore($args)} _ ),*) -> _, but until then the best available option is to use a macro that ignores its input and expands to a fixed output to emulate ${ignore}.

// future
macro_rules! transmute_call {
    ($(extern $abi:tt)? $f:expr $(, $arg:expr)* $(,)?) => {
        (::core::mem::transmute<
            _,
            $(extern $abi)? fn($( ${ignore($arg)} _ ),*) -> _,
        >)($($arg),*)
    };
}

// today
macro_rules! infer_typeof {
    ($_:expr) => { _ };
}
macro_rules! transmute_call {
    ($(extern $abi:tt)? $f:expr $(, $arg:expr)* $(,)?) => {
        (::core::mem::transmute<
            _,
            $(extern $abi)? fn(
                $($crate::infer_typeof!($arg),)*
            ) -> _,
        >)($($arg),*)
    };
}

Calling the helper macro infer_typeof! is a bit of a cute choice to make the usage look less odd; the name isn't incorrect as to what's being done here, it's just maybe misleading if used anywhere else. So a more descriptive name like ty_underscore_for! is probably preferable to being cute.

1 Like

Thanks for the heads up. This is for working with a C api from the Linux kernel, which gives them to me as void pointers.

This is for the rust/kernel/static_call.rs file.

I don't think that would work since the function pointer being used for type inference doesn't have a () argument at the beginning. I don't control its signature as it's generated by bindgen.

Okay.

I will use a helper macro for now and change it to that later.

Thanks!

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.