Idiomatic way to handle pointer to values returned by FFI call?


#1

Hi,

I’m using Rust’s excellent FFI support to call a C function that allocates and returns a pointer to 6 double values (simplified here):

extern {
    #[link_name = "my_lib_coeffs"]
    pub fn raw_coeffs() -> *mut c_double;

    // ...
}

The C library call expects the caller to take ownership of the 6 doubles and delete them at some point. I’d like to create a higher-level Rust function that hides the details of the FFI call and returns the doubles in a Vec.

Is the following (admittedly clunky) attempt an idiomatic way of going about this in Rust?

 pub fn coeffs() -> Vec<f64> {
    let mut result = Vec::with_capacity(6);

    unsafe {
      let values = raw_coeffs();

      result.set_len(6);
      values.copy_to_nonoverlapping(result.as_mut_ptr(), 6);
      std::ptr::drop_in_place(values);

      result
    }
  }

Any advice much appreciated,

Stu


#2

I don’t think drop_in_place is supposed to be used on memory allocated by C-Code. C is going to use the C system allocator and Rust may or may not be using the system allocator (by default Rust does not and instead uses jemalloc). to safely deallocate (free) a pointer to memory allocated by C-Code requires that you call the C “free” function.


#3

Rust’s drop-related functionality is strictly only for values created by Rust itself. Don’t use it on a pointer from C.

To free the data behind the C pointer you should call a C function that does it, so that definitely the same C’s allocator is used. If the pointer comes from C’s malloc, then Rust’s libc::free may work, except it’s dangerous to do so if Windows DLLs are involved, since each DLL may have separate runtime.

Don’t use unsafe set_len. You can use a safe loop with push() — it optimizes well.

You can cast *mut f64 to *mut [f64; 6] if you know there are certainly 6 items there (or std::slice::from_raw_parts() if the length was dynamic).

From there you can use .as_ref().unwrap() to make it a reference, and then .to_owned() or vec.extend(&arr) to copy it to a Vec.


#4

Thanks – that makes much more sense than the example I posted. And thanks to everyone for such a quick response.

What an excellent community!

Stu


#5

Good point. I’ll add an API to the C library to delete instances of the 6 doubles it has created (when asked).

Stu


#6

Seems odd to allocate an array for 6 doubles. Perhaps have the C code take an array from the caller and populate it. Rust can then send a stack allocated array via FFI and nothing needs to be explicitly freed on the C side. Or you can send Rust heap allocated storage and let Rust free it normally.


#7

Good points – thanks.

Stu