Home > front end >  How to cast a void pointer into a function with an arbitrary signature in rust
How to cast a void pointer into a function with an arbitrary signature in rust

Time:06-13

I'm using the libjit library to jit compile functions. After compiling a function to a closure it returns a *mut ::std::os::raw::c_void. This void pointer is a actually a function I could call like so:

For example

let void_ptr = jit_function_to_closure(self.function);
let func_ptr: fn(i32, i32,i32) -> i32 = std::mem::transmute(void_ptr);
func_ptr(1,2,3);

This works fine but requires I know the signature of every function I want to compile.

I want to wrap this snippet in a function that accepts a function pointer type eg. fn(i32, i32,i32) -> i32 as a generic arg so I can return a function pointer of any type specified by the caller.

Something like:

// My library
pub fn to_closure<T>(&self) -> T {
    unsafe {
        let void_ptr = jit_function_to_closure(self.function);
        std::mem::transmute(void_ptr)
    }
}
// Caller
let func = ....
let callable = fn(f64, f64) -> i32 = func.to_closure();

This does not compile

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
  --> src/function.rs:57:13
   |
57 |             std::mem::transmute(void_ptr)
   |             ^^^^^^^^^^^^^^^^^^^
   |
   = note: source type: `*mut c_void` (64 bits)
   = note: target type: `T` (this type does not have a fixed size)

Is this possible? Is there a generic bound for a function pointer of arbitrary signature?

CodePudding user response:

No, there is not trait like that. You have two options:

  • Leave it as-is, but since transmute() cannot be used for unknown-sized types, use the usual workaround - transmute_copy(): unsafe { std::mem::transmute_copy(&void_ptr) }.
  • This is what I would prefer: create a trait to encapsulate the notion of "being a function pointer". Since Rust has no variadics, you'll have to use macros and it will not cover all functions, but it should cover all usable cases. Here I present a macro that only generates impls for the extern "C" calling convention, but adapting it to using other calling conventions should not be difficult:
trait FnPtr {
    unsafe fn from_void_ptr(ptr: *mut c_void) -> Self;
}

macro_rules! impl_fnptr {
    // Recursion exit condition
    () => {};
    ($first:ident $($rest:ident)*) => {
        impl<$first, $($rest,)* Ret> FnPtr for extern "C" fn($first, $($rest,)*) -> Ret {
            unsafe fn from_void_ptr(ptr: *mut c_void) -> Self {
                std::mem::transmute::<*mut c_void, Self>(ptr)
            }
        }
        // With variadic args
        impl<$first, $($rest,)* Ret> FnPtr for extern "C" fn($first, $($rest,)* ...) -> Ret {
            unsafe fn from_void_ptr(ptr: *mut c_void) -> Self {
                std::mem::transmute::<*mut c_void, Self>(ptr)
            }
        }
        
        // Recurse
        impl_fnptr!($($rest)*);
    };
}
impl<Ret> FnPtr for extern "C" fn() -> Ret {
    unsafe fn from_void_ptr(ptr: *mut c_void) -> Self {
        std::mem::transmute::<*mut c_void, Self>(ptr)
    }
}
// Impl up to 15 parameters.
impl_fnptr!(A B C D E F G H I J K L M N O);

Note, however, that the to_closure() function needs to be marked unsafe to be sound, since you can use the wrong signature etc..

  • Related