Sending a boxed trait over FFI

Hi everyone,

I’m trying to create a C-api for interacting with my library, and one thing I’d like to be able to do is pass boxed trait objects across the FFI boundary. To the client, this should appear as an opaque pointer (void *). Suppose I have the following definitions:

trait MyTrait { }
struct MyStruct;
impl MyTrait for MyStruct { }

use std::os::raw::c_void;
pub extern "C" fn create_resource() -> *mut c_void {
    let my_struct = MyStruct;
    let boxed_trait: Box<dyn MyTrait> = Box::new(my_struct);
    let heap_pointer: *mut MyTrait = Box::into_raw(boxed_trait);
    heap_pointer as *mut c_void
}

// Definition #1: works, but exposes MyTrait
pub extern "C" fn use_resource(heap_pointer: *mut MyTrait) {
    let boxed_trait: Box<dyn MyTrait> = unsafe { Box::from_raw(heap_pointer) };
    // Use boxed_trait like normal
}

// Definition #2: does not work, but hides MyTrait
pub extern "C" fn use_resource(heap_pointer: *mut c_void) {
    let trait_heap_pointer: *mut MyTrait = heap_pointer as *mut MyTrait;
    let boxed_trait: Box<dyn MyTrait> = unsafe { Box::from_raw(trait_heap_pointer) };
}

My ultimate goal is to use these definitions to generate a header file using cbindgen, so I would like to use c_void wherever I have an opaque pointer. The second definition of use_resource is the signature that I want, but since Box::from_raw has the signature Box::from_raw(raw: *mut T) -> Box<T>, that means that I must cast the raw pointer into a T (where T is MyTrait) before I can pass it to from_raw.

If I use Box::into_raw(Box<dyn MyTrait>), the output I get is a *mut MyTrait. Since MyTrait is a trait, Rust treats it like an object, and so a *mut MyTrait is a fat pointer. However, since this trait object is boxed, shouldn’t the output of Box::into_raw actually be a thin pointer to the heap (which then contains a fat pointer)?

This creates the problem that my *mut MyTrait that came from Box::into_raw can successfully be cast into a c_void, but the reverse cannot happen, so I’m unable to use a boxed trait object as an opaque handle that my library client can use as a context for other functions to my library.

Additionally, when I try to cast a *mut c_void into a *mut MyTrait, I get the error:

the trait `MyTrait` is not implemented for `std::ffi::c_void`

I thought that raw pointers should be able to be cast to one another without a problem, and that it was dereferencing, not the conversion itself, that was unsafe. I believe this again stems from the fact that Rust is treating the raw pointer to the trait as a “fat” pointer, even though that raw pointer was retrieved from a Box, and so should be a thin pointer.

If anybody has any insight to this I’d greatly appreciate it. I’m willing to consider other solutions, but my original ultimate goal is to have some opaque handle to pass to my C clients, where that handle is also a boxed trait object. Thanks in advance!

2 Likes

No. Every trait object pointer is fat. That’s the only reason Box<dyn Trait> even works in the first place.

If you want a boxed trait object pointer, then you need to actually box the trait object pointer: Box<Box<dyn Trait>>.

4 Likes

Thanks for the reply!

Ok, I didn’t realize that all object pointers are fat. So my actual use case is a bit more complex than the simplified version I gave before, because my original understanding of the problem was a bit misinformed. So let me give some more details about my setup. Let’s say that I have definitions like this:

trait Interface { }
struct DriverA;
struct DriverB;
impl Interface for DriverA { }
impl Interface for DriverB { }

struct Device {
    driverA: DriverA,
    driverB: DriverB,
}
impl Device {
    fn borrowA(&mut self) -> &mut DriverA { &mut self.driverA }
    fn borrowB(&mut self) -> &mut DriverB { &mut self.driverB }
}
// Collection of devices
struct Devices { ... }

pub extern "C" fn get_devices(devices: *mut *mut c_void) {
    let devices = Box::new(Devices::new());
    let devices_pointer = Box::into_raw(devices);
    let devices_handle = devices_pointer as *mut Devices as *mut c_void;
    unsafe { *devices = devices_handle; }
}

// Uses the devices handle from above and an index to choose a device
pub extern "C" fn select_device(devices: *mut c_void, index: u32) -> c_void {
    let devices: Box<Devices> = unsafe { Box::from_raw(devices as *mut Devices) };
    devices.get(index) as *mut Device as *mut c_void
}

pub extern "C" fn get_driverA(device: *mut c_void) -> c_void {
    let device: &mut Device = unsafe { &mut *(device as *mut Device) };
    device.borrowA() as *mut Interface as *c_void
}
pub extern "C" fn get_driverB(...) -> c_void { /* similar */ }

pub extern "C" fn use_interface(iface: *mut c_void) {
    let interface: &mut Interface = unsafe { &mut *(iface as *mut Interface) };
    // Use interface
}

The expected usage for a C client would be something like the following:

void *devices = NULL;
get_devices(&devices);
void *device = select_device(devices, 0);
void *iface = get_driverA(device);
use_interface(iface);

The problem here arises when casting iface as *mut Interface, even though in get_driverA there was no problem casting *mut Interface as *mut c_void. I still have to figure out the problem regarding the error:

the trait `Interface` is not implemented for `std::ffi::c_void`

By my understanding, I thought I should be able to cast any raw pointer to a raw pointer of another type, then any unsafe repercussions would appear when dereferencing the raw pointer at the end. Is there any way to do what I’m trying to do here? If it’s not possible exactly how I’m approaching it, are there any other strategies I could try?

I assume that’s not your actual code, since you’re returning the wrong types (c_void instead of *mut c_void).

Again, trait objects are all fat. You cannot cast between thin pointers and fat pointers without permanently losing information. It’s like casting between u64 and u32, and being surprised that you lose information.

That error is because you don’t simply cast to a trait object, you have to construct a trait object out of a concrete pointer. The compiler is rightly pointing out that it can’t do this with c_void. Being able to cast between raw pointers only works for pointers to Sized types.

You need to Box the *mut Interface, then return that. In use_interface, you turn iface into *mut *mut Interface and use that.

2 Likes

If you don’t want a double pointer, another option might be to use the thin crate:

Provides thin pointer types to dynamically sized types that implement the DynSized trait. By “thin”, that means they store the metadata (i.e. slice length or vtable pointer) together with the data, instead of in the pointer itself. Currently provides ThinBox , a thin version of Box .

Yes, you’re right. I should have run that through a compiler before posting it - I was loosely transcribing my problem in generic terms.

Ah, that makes sense. I ended up putting together something like this:

pub extern "C" fn get_driverA(device: *mut c_void) -> *mut c_void {
    let device: &mut Device = unsafe { &mut *(device as *mut Device) };
    let iface = device.borrowA() as *mut Interface;
    let boxed_iface = Box::new(iface);
    Box::into_raw(boxed_iface) as *mut c_void
}

pub extern "C" fn use_interface(iface: *mut c_void) {
    let interface: Box<Box<dyn Interface>> = unsafe { Box::from_raw(iface as *mut _) };
    // use interface
}

This seems to behave exactly as I need!

Thanks a ton for your help, this had been puzzling me for awhile :slight_smile:

Thanks for pointing that out to me, I may end up using it in the future :slight_smile: