C library freeing a pointer coming from Rust

2019-04-11 02:34发布

问题:

I want to do Rust bindings to a C library which requires a callback, and this callback must return a C-style char* pointer to the C library which will then free it. The callback must be in some sense exposed to the user of my library (probably using closures), and I want to provide a Rust interface as convenient as possible (meaning accepting a String output if possible).

However, the C library complains when trying to free() a pointer coming from memory allocated by Rust, probably because Rust uses jemalloc and the C library uses malloc.

So currently I can see two workarounds using libc::malloc(), but both of them have disadvantages:

  • Give the user of the library a slice that he must fill (inconvenient, and imposes length restrictions)
  • Take his String output, copy it to an array allocated by malloc, and then free the String (useless copy and allocation)

Can anybody see a better solution?

Here is an equivalent of the interface of the C library, and the implementation of the ideal case (if the C library could free a String allocated in Rust)

extern crate libc;
use std::ffi::CString;
use libc::*;
use std::mem;

extern "C" {
    // The second parameter of this function gets passed as an argument of my callback
    fn need_callback(callback: extern fn(arbitrary_data: *mut c_void) -> *mut c_char,
                     arbitrary_data: *mut c_void);
}

// This function must return a C-style char[] that will be freed by the C library
extern fn my_callback(arbitrary_data: *mut c_void) -> *mut c_char {
    unsafe {
        let mut user_callback: *mut &'static mut FnMut() -> String = mem::transmute(arbitrary_data); //'
        let user_string = (*user_callback)();
        let c_string = CString::new(user_string).unwrap();
        let ret: *mut c_char = mem::transmute(c_string.as_ptr());
        mem::forget(c_string); // To prevent deallocation by Rust
        ret
    }
}

pub fn call_callback(mut user_callback: &mut FnMut() -> String) {
    unsafe {
        need_callback(my_callback, mem::transmute(&mut user_callback));
    }
}

The C part would be equivalent to this:

#include <stdlib.h>
typedef char* (*callback)(void *arbitrary_data);
void need_callback(callback cb, void *arbitrary_data) {
    char *user_return = cb(arbitrary_data);
    free(user_return); // Complains as the pointer has been allocated with jemalloc
}

回答1:

It might require some annoying work on your part, but what about exposing a type that implements Write, but is backed by memory allocated via malloc? Then, your client can use the write! macro (and friends) instead of allocating a String.

Here's how it currently works with Vec:

let mut v = Vec::new();
write!(&mut v, "hello, world");

You would "just" need to implement the two methods and then you would have a stream-like interface.



标签: rust ffi