Sorry if I'm missing something, but I've gotten a bit confused based off of how the compiler is responding to some errors.
I'm trying to pass a Box<dyn std::io::Read> object into FFI, and then I have to get that object back from a callback the FFI calls. The following code isn't quite what I'm actually using, but it illustrates the issue:
use std::{ffi, io::{self, Cursor}};
// Most types have been manually annotated for clarity.
fn ffi_reading_stuff<R: io::Read>(reader: &mut R) {
// Convert the trait into a raw pointer of a `dyn io::Read` object.
let boxed_reader: Box<&mut dyn io::Read> = Box::new(reader);
let reader_ptr: *mut dyn io::Read = Box::into_raw(boxed_reader);
// Conver the pointer into a `ffi::c_void` pointer so that we can pass it into an FFI function.
let void_ptr: *mut ffi::c_void = reader_ptr as *mut ffi::c_void;
/// Do FFI stuff here...
// Receive the pointer back from FFI, and cast it back into the `dyn io::Read` object so that we can work with it properly.
//
// Alas this fails because "`c_void: std::io::Read` is not satisfied", but why?
let returned_reader_ptr: *mut dyn io::Read = void_ptr as *mut dyn io::Read;
}
fn main() {
// Pretend this is the file I'm trying to read.
let mut file: Cursor<Vec<u8>> = Cursor::default();
ffi_reading_stuff(&mut file)
}
Is there any reason that *mut type cast at the bottom of fn ffi_reading_stuff fails?
Pointers to trait objects are fat pointers because they contain both a pointer to the data and a pointer to the vtable needed for dynamic dispatch. The vtable pointer can only be created from statically-known type information when the trait object is created from the concrete type implementing the trait. There is simply not enough information in a bare data pointer for the compiler to fill in the vtable pointer: it simply isn't there.
Here, though, the Box<&mut dyn io::Read> is actually a thin pointer that's being coerced back into a fat pointer, possibly by accident. This example can be fixed by changing the type of reader_ptr to *mut &mut dyn io::Read, but it feels quite awkward: Since this code knows the type R concretely, there's no real reason to use a trait object at all.
// Most types have been manually annotated for clarity.
fn ffi_reading_stuff<R: io::Read>(reader: &mut R) {
// Convert the trait into a raw pointer of a `dyn io::Read` object.
let boxed_reader: Box<&mut dyn io::Read> = Box::new(reader);
let reader_ptr: *mut &mut dyn io::Read = Box::into_raw(boxed_reader);
// Conver the pointer into a `ffi::c_void` pointer so that we can pass it into an FFI function.
let void_ptr: *mut ffi::c_void = reader_ptr as *mut ffi::c_void;
/// Do FFI stuff here...
// Receive the pointer back from FFI, and cast it back into the `dyn io::Read` object so that we can work with it properly.
//
// Alas this fails because "`c_void: std::io::Read` is not satisfied", but why?
let returned_reader_ptr: Box<&mut dyn io::Read> = unsafe {
Box::from_raw(void_ptr as *mut &mut dyn io::Read)
};
// Use the pointer, so Miri will check if we did everything right
let _ = returned_reader_ptr.read(&mut [0u8]);
}
Yeah, that looks right! I don't know how I overlooked that in my actual code, but that definitely looks like what I was trying to do - my actual code accepts that &mut R argument and passes it around like I had in my example, and it looks like I was just coercing the pointer types incorrectly like you showed.
It's a bit of a separate topic, but I knew that dyn Trait objects involved fat pointers somehow, my brain was just considering *mut dyn io::Read to be a pointer of a fat pointer. Is that not the case? I guess I assumed anything marked with *const/*mut would behave like a C pointer, and could thus be converted into any type.
There's actually multiple separate functions calls in my actual code, some of which only receive a *mut ffi::c_void argument which then have to be coerced back into that trait object. That's my bad for the confusion, I was just trying to simplify things for the example I was giving.
Well yeah, but I've always been able to convert between pointer types (i.e. *mut ffi::c_void as *mut String), and I didn't really see the difference for dyn Trait objects.
The underlying types are different of course, but so could the sizes of different underlying structs like *mut String and *mut Vec, and at that point I thought the sizes of the underlying type (and logically including the sizes of things like fat pointers) didn't matter.
dyn io::Read is a dynamically-sized type, which means it can only exist behind a pointer, and that pointer will turn into a fat pointer, whatever flavor it is (*const, *mut, Box, &, &mut, etc.)
In order to have a thin pointer, you need explicit double-indirection. I don't know what your actual code looks like, but the lifetime of reader means that you need to stop using it by the end of this function and the Box is unnecessary. You could have just as easily said:
let reader_ptr = (&mut reader) as *mut &mut dyn io::Read;
String and Vec are fixed-size types which contain fat pointers, and so *mut String is effectively serving this same double-indirection purpose.
This has nothing to do with the size of the pointed type. C has no comparable constructs, so trying to study C to understand this behavior is entirely useless.
Again, a *mut dyn Trait is vtable pointer + a data pointer, whereas a *mut c_void is just a data pointer. You can throw away the vtable pointer and cast from *mut dyn Trait to *mut c_void, but you can't create a vtable pointer out of nothing, and thus you can't cast from *mut c_void to *mut dyn Trait.
My actual code is here (plus the functions below it) and here, but you have to jump to a lot of places in the codebase to understand what's going on, so I chose to avoid sharing it. I was doing the Box stuff so that I could pass a dyn Trait object into FFI code, as it seemed the cleanest. Regardless, I'm pretty confident that the solution you gave is what I needed to be using in my code.
I haven't tried to follow the whole control flow, but I suspect your code has a small memory leak: You allocate space on the heap for bcontainerhere, but I can't find where you ever deallocate it (by reconstructing the box and dropping it).
Also, constructing and then forgetting a Box in HuskRustGetByteFromReadTrait feels off— If the read call panics, you'll unexpectedly deallocate because you haven't yet called forget. It's probably better to make an &mut directly here:
let bcontainer: &mut ReadContainer = &mut *(container as *mut ReadContainer);