Extract function pointer and data pointer from a closure

How do I extract the function pointer from Fn closure? Using unsafe is fine, of course.

fn convert<F: Fn(f64) -> i32>(f: &F) -> fn(*const T, f64) -> i32 {
}

I can convert the closure to a &dyn Fn and look at the virtual table. Is there a way to do that without being too dependent on compiler implementation?

Also, how to get a pointer to the drop function to the closure? Can it be done without creating a Box<dyn Fn> first?

With a bit more searching found a blog post which solves the problem.

Thanks Sean!

2 Likes

You can't. F might be a closure that contains additional data beyond the function pointer itself.

I know there could be data associated. The closure is represented as data + a function. The function will of course takes a pointer to the data as the first argument. I knew how to get a raw pointer to the data. I was wondering how to get a pointer to the function reliably.

Btw, note that the returned function pointer in the question above takes a *const T as the first argument.

fn get_virtual_call_fn_pointer<F> ()
  -> fn(&'_ F, f64) -> i32
where
    F : Fn(f64) -> i32,
{
    |f: &'_ F, arg: f64| -> i32 { f(arg) }
}

you will notice that providing an actual instance f: &'_ F is not even needed; the "virtual method" can be extracted from the static type knowledge of F.

What you are looking for is getting a &'lt dyn Fn(f64) -> i32 in a FFI-compatible way:

use ::core::ffi::c_void;

#[derive(Clone, Copy)]
#[repr(C)]
/// ```c
/// typedef struct {
///     void const * data;
///     int32_t (*fptr) (void const * data, double arg);
/// } dyn_fn_f64_to_i32_t;
/// ```
pub
struct DynFnF64ToI32<'lt> {
    data: *const c_void,
    fptr: Option<
        unsafe extern "C"
        fn (data: *const c_void, arg: f64) -> i32
    >,
    _lt: ::core::marker::PhantomData<&'lt ()>,
}

impl<'lt> DynFnF64ToI32<'lt> {
    pub
    fn from<F : 'lt + Fn(f64) -> i32> (f: &'lt F) -> Self
    {
        Self {
            data: <*const F>::cast::<c_void>(f),
            _lt: Default::default(),
            fptr: Some({
                unsafe extern "C"
                fn call<F : Fn(f64) -> i32> (data: *const c_void, arg: f64) -> i32
                {
                    ::scopeguard::defer_on_unwind!({
                        ::std::process::abort();
                    });
                    let f: &'_ F =
                        data.cast::<F>()
                            .as_ref()
                            .expect("Got NULL `data` pointer")
                    ;
                    f(arg)
                }
                call::<F>
            }),
        }
    }

    /** ```C
    int32_t call (dyn_fn_f64_to_i32_t self, double arg)
    {
        if (self.fptr == NULL) {
            fprintf(stderr, "Fatal error: got NULL fptr\n");
            exit(EXIT_FAILURE);
        }
        return (self.fptr)(self.data, arg);
    }
    ``` **/
    pub
    fn call (self: Self, arg: f64) -> i32
    {
        unsafe {
            let fptr = self.fptr.expect("Got NULL `fptr`");
            fptr(self.data, arg)
        }
    }
}
  • (No drop because of the borrowed access).

  • Playground

2 Likes

Thanks for fleshing out the solution! I need both the closure fn ptr and drop fn ptr. But the same approach works.

Well the post you linked does already handle the Box<dyn Fn (instead of &dyn Fn) case, so you should be covered up indeed :slightly_smiling_face:

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.