FFI - C FILE* and good Rust wrapper equivalent type

In general, I strongly advise against type punning on the pointee, while keeping Rust high level pointers around:

  • it is unsound to transmute a &mut Repr<()> to a &mut Repr<SomethingNot0SizedOrNot1Aligned>,

    • Ditto for the other high level pointers (e.g., & _, Box<_>)

    • More precisely: it is unsound to dereference any pointer that was created from casting to a Repr<T> a pointer that originated from a &[mut] Repr<()> . See pointer provenance and the following issue.

      So it's at least a safety invariant. It may even be a validity invariant, but that part is not settled yet (see also the issue for Box).

In practice, thus, it is best to hard-code the pointer type (in this example, Box, inside the wrapper type (FileHandle), and only deal with raw pointers from that moment onwards:

pub
struct BoxedFileHandle {
    ptr: ptr::NonNull<Repr<()>>, // represents a "Box<Repr<()>>",
}

impl BoxedFileHandle {
    pub
    fn for_writer<W : Write + 'static> (writer: W)
      -> BoxedFileHandle
    {
        let repr: Repr<W> = Repr { vtable: ..., data: writer };
        Self {
            ptr: ptr::NonNull::from(Box::leak(Box::new(repr))).cast(),
        }
    }
}

impl Drop for BoxedFileHandle {
    fn drop (self: &'_ mut BoxedFileHandle)
    {
        let VTable { destroy, .. } = self.vtable();
        unsafe { destroy(self.ptr); } // assuming `destroy` also deallocates the Box
        /* destroy would be `drop::<Box<Repr<OriginalType>>>(Box::from_raw( _ ))` */
    }
}

impl BoxedFileHandle {
    #[inline]
    fn vtable (self: &'_ BoxedFileHandle)
      -> VTable
    {
        // This is the only time we create a Rust high-level pointer
        // to a `Repr<()>`. Although error-prone, here this is fine, because:
        //   - such high level pointer no longer exists by the time the function returns
        //   - Reading a `Repr<()>` out of a `Repr<W>` is well-defined (thanks to `#[repr(C)]`). 
        unsafe { self.ptr.as_ref().vtable }
    }

    pub
    fn into_ffi (self: BoxedFileHandle)
      -> *mut ::core::ffi::c_void
    {
        self.ptr.cast().as_ptr()
    }

    pub
    unsafe
    fn from_ffi (ptr: *mut ::core::ffi::c_void)
      -> BoxedFileHandle
    {
        Self { ptr: ptr::NonNull::new(ptr.cast()).expect("Got NULL") }
    }
}
1 Like