12

I'm writing a library in Rust that has a C interface. C side must be able to create and destroy Rust objects (C side owns them and controls their lifetime).

I've managed to "leak" an object to C, but I'm not sure how to properly free it:

pub extern "C" fn create() -> *mut Foo {
   let obj = Foo; // oops, a bug
   let ptr = std::mem::transmute(&mut obj); // bad 
   std::mem::forget(obj); // not needed
   return ptr;
}

pub extern "C" fn destroy(handle: *mut Foo) {
   // get Foo back and Drop it??? 
}

I'm not sure how can I turn pointer back to an object that Rust will call Drop on. Simply dereferencing *handle doesn't compile.

Kornel
  • 97,764
  • 37
  • 219
  • 309

2 Answers2

15

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
}
Kornel
  • 97,764
  • 37
  • 219
  • 309
  • Doesn't one need to put an `unsafe` block around `foo.as_ref().unwrap()`? – Amos Egel Sep 26 '18 at 12:49
  • @AmosEgel Indeed. I've marked these whole functions with the `unsafe` keyword, because it's caller's responsibility to ensure these pointers are safe to use. – Kornel Sep 28 '18 at 13:49
  • Given that it's the caller's responsibility to ensure that pointers passed to `do` are safe to use, could one not simply define its parameter to be `&mut Foo`? Or does Rust not guarantee that references are internally represented as pointers? If so, do similar guarantees exist for `Box` such that `free_foo`'s parameter could be `Box` and then the function need merely call `::std::mem::drop`? Then `null`-safety might even be added to either signature with `Option`? – eggyal Dec 09 '18 at 07:12
  • @eggyal In this case you might use `&mut` to borrow from C, if the C side promises to never pass `NULL`. Rust's references can never be allowed to be `null` or point to uninitialized memory at any time. Rust's references and `Box`es may sometimes be a "fat" pointer, i.e. a 2-usize struct, but that is never allowed in C FFI, so not a problem here. – Kornel Dec 09 '18 at 16:07
  • @Kornel: but if I’ve understood correctly `Option<&mut Foo>` would translate directly to/from a nullable pointer? I haven’t read enough how Boxes are implemented though. – eggyal Dec 09 '18 at 18:52
  • @eggyal It does have the same binary representation. I think it's guaranteed to be FFI-compatible for `Option`, but I don't remember if that's also guaranteed for references. See [nomicon](https://doc.rust-lang.org/nomicon/). – Kornel Dec 10 '18 at 13:00
10

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);
}
trent
  • 25,033
  • 7
  • 51
  • 90
DK.
  • 55,277
  • 5
  • 189
  • 162
  • 3
    `&mut T` to `*mut T` does not require transmutation; a simple `as *mut _` will do. – Chris Morgan Feb 02 '15 at 13:39
  • 4
    Why the asymmetry? `create` returns a `*mut Dramatic` but `destroy` takes a `&mut *mut Dramatic` whereas in C you would expect similar types `Dramatic*` for both. – Matthieu M. Feb 02 '15 at 14:39
  • 4
    @MatthieuM. "I am going to null out the `ptr` so the calling code is less likely to accidentally re-use the pointer". It's a convention I've seen in C, but I've never really liked it. – Shepmaster Feb 02 '15 at 14:49
  • 1
    @Shepmaster: Neither have I, it's a false sense of security since any copy of the pointer is unchanged anyway. – Matthieu M. Feb 02 '15 at 14:50
  • 1
    @ChrisMorgan: When I tried that, the borrow checker complained that I couldn't forget `obj` while it was borrowed... until I thought to insert a `: *mut _` constraint on the variable. – DK. Feb 02 '15 at 15:10
  • @MatthieuM.: It's true that it's far from fool-proof, but it makes it a *little* clearer that the pointer your passing is going to be destroyed. – DK. Feb 02 '15 at 15:12
  • 1
    @DK.: at the cost of having a signature that is incompatible with `free` which is what most APIs expect. – Matthieu M. Feb 02 '15 at 16:45