How to lend a Rust object to C code for an arbitrary lifetime?

To send a Rust object to C:

#[no_mangle]
pub extern "C" fn create_foo() -> *mut Foo {
    Box::into_raw(Box::new(Foo))
}

or taking advantage of Box being FFI-safe and the same as a pointer, and the fact that Rust function definitions do not have to match C headers exactly, as long as the ABI is the same:

#[no_mangle]
pub extern "C" fn create_foo() -> Box<Foo> {
   Box::new(Foo)
}

(returning Option<Box<Foo>> is fine too. Result is not.)

To borrow (and not free) from C:

#[no_mangle]
pub unsafe extern "C" fn peek_at(foo: *mut Foo) {
    let foo = foo.as_ref().unwrap(); // That's ptr::as_ref
}

or taking advantage of references and Option being FFI-safe:

#[no_mangle]
pub extern "C" fn peek_at(foo: Option<&mut Foo>) {
    let foo = foo.unwrap();
}

To take over/destroy Rust object previously given to C:

#[no_mangle]
pub unsafe extern "C" fn free_foo(foo: *mut Foo) {
    assert!(!foo.is_null());
    Box::from_raw(foo); // Rust auto-drops it
}

or using the fact that Option<Box> is FFI-safe, and memory-managed by Rust:

#[no_mangle]
pub unsafe extern "C" fn free_foo(foo: Option<Box<Foo>>) {
   // dropped implicitly
}

Actually, you haven't managed to leak an object to C; you've managed to leak a reference to a (shortly) non-existent stack frame. :D

Here's a full example that should work correctly. I've tried to comment it as appropriate to explain what I'm doing and why.

pub struct Dramatic(String);

// Implement a destructor just so we can see when the object is destroyed.
impl Drop for Dramatic {
    fn drop(&mut self) {
        println!("And lo, I, {}, meet a most terrible fate!", self.0);
    }
}

pub extern "C" fn create() -> *mut Dramatic {
    // We **must** heap-allocate the object!  Returning a reference to a local
    // will **almost certainly** break your program!
    let mut obj = Box::new(Dramatic("Roger".to_string()));

    // into_raw turns the Box into a *mut Dramatic, which the borrow checker
    // ignores, without calling its destructor.
    Box::into_raw(obj)
}

pub extern "C" fn destroy(ptr: &mut *mut Dramatic) {
    // First, we **must** check to see if the pointer is null.
    if ptr.is_null() {
        // Do nothing.
        return;
    }

    // Now we know the pointer is non-null, we can continue. from_raw is the
    // inverse of into_raw: it turns the *mut Dramatic back into a
    // Box<Dramatic>. You must only call from_raw once per pointer.
    let obj: Box<Dramatic> = unsafe { Box::from_raw(*ptr) };

    // We don't *have* to do anything else; once obj goes out of scope, it will
    // be dropped.  I'm going to drop it explicitly, however, for clarity.
    drop(obj);

    // I am, however, going to null out the `ptr` we were passed just so the
    // calling code is less likely to accidentally re-use the pointer.
    *ptr = ::std::ptr::null_mut();
}

fn main() {
    let mut ptr = create();
    println!("ptr = {:?}", ptr);
    destroy(&mut ptr);
    println!("ptr = {:?}", ptr);
}

Tags:

C

Ffi

Rust