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!