Я реализую метод обратного вызова, чтобы заменить использование дженериков.
Для максимальной производительности нельзя использовать Box и я также не хочу использовать dyn FnMut(T) -> Ret как это вводит vtable.
Поэтому я решил написать не принадлежащий ErasedFnPointer в котором хранится указатель функции и указатель на struct если указатель функции является связанной функцией.
Это не так уж и сложно, поскольку связанная функция в ржавчине — это просто обычная функция.
Чтобы сделать его меньше, я использую transmute конвертировать fn(&self, T) -> Ret к fn(*mut c_void, T) -> Ret, который работает, однако я не уверен в его правильности.
Мне также интересно, существует ли другой подход, более производительный и компактный.
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
}
}
```
