generics – A safe type-erased Fn Pointer in rust that can be used to call associated and regular function

I am implementing a callback method to replace the use of generics.

For maximum performance, it is not allowed to use Box and I also don’t want to use dyn FnMut(T) -> Ret as that introduces vtable.

So I decided to write a non-owning ErasedFnPointer that stores the function pointer and the pointer to the struct if the function pointer is an associated function.

It isn’t that difficult since associated function in rust is just regular function.

In order to make it smaller, I uses transmute to convert fn(&self, T) -> Ret to fn(*mut c_void, T) -> Ret, which does run, however I am not so sure about its correctness.

I also wonder whether there exists other approach that is more performant and more compact.

use core::ffi::c_void;
use core::mem::transmute;
use core::ptr::null_mut;
use core::marker::PhantomData;

/// ErasedFnPointer can either points to a free function or associated one that
/// `&mut self`
struct ErasedFnPointer<'a, T, Ret> {
    struct_pointer: *mut c_void,
    fp: *const (),
    // The `phantom_*` field is used so that the compiler won't complain about
    // unused generic parameter.
    phantom_sp: PhantomData<&'a ()>,
    phantom_fp: PhantomData<fn(T) -> Ret>,
}

impl<'a, T, Ret> ErasedFnPointer<'a, T, Ret> {
    pub fn from_associated<S>(struct_pointer: &'a mut S, fp: fn(&mut S, T) -> Ret)
        -> ErasedFnPointer<'a, T, Ret>
    {
        ErasedFnPointer {
            struct_pointer: struct_pointer as *mut _ as *mut c_void,
            fp: fp as *const (),
            phantom_sp: PhantomData,
            phantom_fp: PhantomData,
        }
    }
    
    pub fn from_free(fp: fn(T) -> Ret) -> ErasedFnPointer<'static, T, Ret> {
        ErasedFnPointer {
            struct_pointer: null_mut(),
            fp: fp as *const (),
            phantom_sp: PhantomData,
            phantom_fp: PhantomData,
        }
    }
    
    pub fn call(&self, param: T) -> Ret {
        if self.struct_pointer.is_null() {
            let fp = unsafe { transmute::<_, fn(T) -> Ret>(self.fp) };
            fp(param)
        } else {
            let fp = unsafe { transmute::<_, fn(*mut c_void, T) -> Ret>(self.fp) };
            fp(self.struct_pointer, param)
        }
    }
}

fn main() {
    let erased_ptr = ErasedFnPointer::from_free(|x| {
        println!("Hello, {}", x);
        x
    });
    erased_ptr.call(2333);
    
    println!("size_of_val(erased_ptr) = {}", core::mem::size_of_val(&erased_ptr));

    ErasedFnPointer::from_associated(
        &mut Test { x: 1},
        Test::f
    ).call(1);
    
    let mut x = None;
    ErasedFnPointer::from_associated(&mut x, |x, param| {
        *x = Some(param);
        println!("{:#?}", x);
    }).call(1);
}

struct Test {
    x: i32
}
impl Test {
    fn f(&mut self, y: i32) -> i32 {
        let z = self.x + y;
        println!("Hello from Test, {}", z);
        z
    }
}
```