How to correctly use a release of a sharable FFI struct?

I have an external ABI for FFI with the following

#[repr(C)]
#[derive(Debug)]
pub struct Schema {
    name: *const ::std::os::raw::c_char,
    children: *mut *mut Schema,
    release: ::std::option::Option<unsafe extern "C" fn(arg1: *mut Schema)>,
    private_data: *mut ::std::os::raw::c_void,
}

where release is a callback to release, which is responsible for releasing all members, potentially recursively. The provided rules are that the release is expected to be called when the struct should be freed.

We want to use and share the data inside this struct across multiple objects. I only receive a pointer *mut Schema with this struct.

How do I correctly handle this?

I tried a couple of things:

Wrap under an Arc

  • Arc<Schema> to share.
  • impl Drop for Schema and call the release from it.

Challenge: I will need to deference the pointer to wrap it in the Arc, which requires Copy; Copy and Drop are incompatible

Wrap the pointer under an Arc

  • Arc<*mut Schema> or Arc<&'static mut Schema> to share.

Challenge: I was unable to correctly drop this struct, as there is no callback I can use for it.

Any ideas?

Do you own the struct? If so, use std::ptr::read().

What about adding another wrapper which implements Drop?

#[repr(C)]
struct Schema {
  destroy: Option<unsafe extern "C" fn(*mut Schema)>,
  ...
}

extern "C" {  fn some_external_function() -> *mut Schema; }

/// A Rust wrapper which manages a [`Schema`]'s lifetime and gives it
/// convenient methods or trait implementations.
struct SchemaHandle(*mut Schema);

impl Drop for SchemaHandle {
  fn drop(&mut self) {
    if let Some(destroy) = (*self.0).destroy {
      unsafe { destroy(self.0); }
    }
  }
}

fn main() {
  unsafe {
    let schema = some_external_function();
    let handle = SchemaHandle(schema);
    let shared = Arc::new(handle);
  }
}

Depending on the implementation of destroy() either you or the destroy() function will need to deallocate the top Schema's memory. If you need to implement it, just add a libc::free(self.0) at the very end of the Drop::drop() method.

1 Like