Idiomatic usage of Arc in C FFI

141 views Asked by At

I want to implement a FFI for C-Code which calls Functions written in Rust and stores results in Rust-objects for later use.

  • The Rust-Code returns references to objects as Arc<T> where T equals the type of the Rust-objects storing the date.
  • The C-Code has to create new Rust-objects, read out their data and free the memory when it's done.
  • To that end, I cast the Arc<T> into a raw pointer *const T and pass this pointer to C I learned that due to the ABI compatibility, C can interpret *const T as a *void pointer. My concern is the idiomatic and memory safe handling of the Arc and it's referenced object within C.

So far, I've come up with this

use std::sync::Arc;

pub struct my_struct {
    // some fields
}

pub fn init_my_struct(/*some parameters*/) -> Arc<my_struct> {
    // do stuff
    new_struct // new struct is of type Arc<my_struct> with field values initialized
}

pub extern "C" fn c_wrap_init_my_struct(/*some parameters*/) -> *const my_struct {
    // cast to raw pointer, hold by C
    Arc::into_raw(init_my_struct(/*some parameters*/))
}

pub extern "C" fn c_wrap_get_data_from_struct(my_struct_ptr: *const my_struct) -> /*some data type*/
{
    // reconstruct the rust object to extract a value
    // increment strong counter before re-constructing the object from pointer
    unsafe { Arc::increment_strong_count(my_struct_ptr) };
    // mys has ref count two due to increment above
    let mys = unsafe { Arc::from_raw(my_struct_ptr) };
    // do stuff to extract value from mys
    value // return value
} // mys goes out of scope and ref count of my_struct_ptr is decremented to one (2-1=1).

pub extern "C" fn c_wrap_drop_my_struct(my_struct_ptr: *const my_struct) {
    unsafe {
        assert!(!my_struct_ptr.is_null());
        drop(Arc::from_raw(my_struct_ptr)); // ref count drops to zero --> object is discarded.
    }
}

I'm not sure whether manually incrementing the raw pointer's reference count is safe and idiomatic? Without the increment, the reference count in the getter c_wrap_get_data_from_struct would drop to zero and Rust would destroy the object (which I don't want to happen).

1

There are 1 answers

0
cafce25 On

The functions c_wrap_get_data_from_struct and c_wrap_drop_my_struct need to be marked unsafe. Apart from that your code is sound (I think that's what you're asking for, it most definitely is not safe). I would however recommend to use ManuallyDrop instead of manually incrementing and decrementing the reference counter. For one that's semantically not what's happening, and for the other that does not require you to think through and prove another set of safety invariants you need to uphold:

use std::sync::Arc;
use std::mem::ManuallyDrop;
/// # Safety
/// The pointer must have been obtained through `Arc::into_raw`, and the
/// associated `Arc` instance must be valid (i.e. the strong count must
/// be at least 1) when invoking this function. This function should not
/// be called after the final Arc has been released.
#[no_mangle]
pub unsafe extern "C" fn c_wrap_get_data_from_struct(my_struct_ptr: *const MyStruct) -> i32 {
    // reconstruct the Rust object to extract a value
    // wrap it in `ManuallyDrop` because we do not want to consume the `Arc` (decrement the reference count).
    let mys = ManuallyDrop::new(unsafe { Arc::from_raw(my_struct_ptr) });
    mys.value // extract and return value
}

Another couple of notes:

  • init_my_struct should be an associated function MyStruct::init (or new)
  • If you want to call them from C your wrappers need to stop the compiler from mangling their names with #[no_mangle]
  • In Rust type names should use camel case MyStruct
  • It's unclear why c_wrap_drop_my_struct asserts the pointer is not null, but c_wrap_get_data_from_struct doesn't