For the sake of handing a callback trampoline to C++, I wanted to be able to turn &dyn SomeTrait into two thin *mut c_void values, and reassemble it again later with std::ptr::from_raw_parts. But I was surprised to find that DynMetadata was an opaque struct with no accessor for a thin pointer to the vtable. I suppose I could transmute it, but is there any supported way to do what I'm looking for?
The reason it is an opaque struct is because we don't actually guarantee that it is a single pointer big. For example for dyn Foo + Bar we may decide to use two separate vtables in the future. In the standard library the one place I know of where we pass Box<dyn Foo> over an FFI boundary, we do so by boxing it another time to get Box<Box<dyn Foo>> and then convert this to *mut Box<dyn Foo> which is guaranteed to be a single pointer big.
There are enough guarantees and std methods to determine the vtable pointer for the dyn pointers we have today. See this playground I made in response to this thread. (It does use transmute, but also uses pointer methods to ensure which half is the data pointer, and has various other assertions.) @Vorpal also mentioned this crate in that thread (but I haven't checked it out).
On nightly, could someone create a type with pointer metadata that includes uninit bytes?
Looks like the answer is no. Hopefully any future RFC to add pointers with custom metadata would force the metadata to be uninit-free.
if you have the static type when you define the trampoline, you can shift the indirection call (a.k.a virtual dispatch) from the trait object vtable to the callback function pointer by monomophizing the function itself, then you would only need the thin pointer (the data pointer).
here's an example how I would usually handle ffi callbacks. note this example demonstrate the use of a borrowed object, since your question mentioned &dyn SomeTrait, but in practice, boxed objects are way more common because most ffi callbacks are asynchronously delivered events.
mod ffi {
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct CallbackHandle(isize);
unsafe extern "C" {
pub fn register(f: extern "C" fn(*const c_void, i32) -> i32, d: *const c_void) -> CallbackHandle;
pub fn unregister(handle: CallbackHandle) -> *const c_void;
}
}
/// a guard object to unregister the callback
struct Guard<'b, T> {
handle: ffi::CallbackHandle,
_marker: PhantomData<&'b T>,
}
/// the constructor and destructor
impl<'b, T> Guard<'b, T> {
fn new(handle: ffi::CallbackHandle) -> Self { todo!() }
fn unregister(self) -> &'b T {
let me = ManuallyDrop::new(self);
let ptr = unsafe { ffi::unregister(me.handle) };
unsafe { &*ptr.cast() }
}
}
impl<'b, T> Drop for Guard<'b, T> {
fn drop(&mut self) {
unsafe { ffi::unregister(self.handle) };
}
}
/// the safe rust api for the callback
trait CallbackTrait {
fn callback_method(&self, arg: i32) -> i32;
}
/// IMPORTANT: the returned guard must not be forgotten
unsafe fn register_callback<T: CallbackTrait>(obj: &T) -> Guard<'_, T> {
extern "C" fn cb<T: CallbackTrait>(obj: *const c_void, arg: i32) -> i32 {
// SAFETY: the register procedure guarantees the correct type T
let obj: &T = unsafe { &*obj.cast() };
obj.callback_method(arg)
}
let handle = unsafe { ffi::register(cb::<T>, obj as *const T as *const c_void) };
Guard::new(handle)
}
Yep that's what I wound up doing, although a custom generic trampoline function amounts to custom dynamic dispatch, which feels unfortunate when the language already has dynamic dispatch built in.