I would like to implement a function which is exposed as a C function via FFI, returning a C string. What would be the correct memory management model to use?
My first try was something along the lines:
// In .h
extern const char* const get_string();
// In .rs
#[no_mangle]
pub extern fn get_string() -> *const c_char {
let s = String::from_str("Hello!");
let cs = CString::from_slice(s.as_slice().as_bytes());
cs.as_ptr()
}
But since the lifetime of pointer returned from CString ends as soon as cs goes out of scope, that cannot be the correct way, right? (although it seems to compile just fine). What is the recommended way to do this?
Hm, that's a complicated question. Basically, like this: Rust Playground
extern crate libc;
use libc::c_char;
use std::ffi::CString;
use std::sync::{Once, ONCE_INIT};
use std::mem::transmute;
use std::ptr::read;
static START: Once = ONCE_INIT;
static mut data:*const CString = 0 as *const CString;
#[no_mangle]
pub extern fn get_string() -> *const c_char {
START.call_once(|| {
unsafe {
let boxed = Box::new(CString::from_slice("Hello World".as_bytes()));
data = transmute(boxed);
}
});
unsafe {
return (&*data).as_ptr();
}
}
pub fn free_resources() {
unsafe {
let _ = transmute::<*const CString, Box<CString>>(data);
data = 0 as *const CString;
}
}
fn main() {
println!("{:?}\n{:?}\n{:?}", get_string(), get_string(), get_string());
free_resources();
}
You need do two things:
Move the value on to the heap so it is not lost when the stack frame is dropped.
Move the value out of the rust memory management model.
Every Box is equivalent to a heap allocated *const T, and can calling transmute() consumes the value, turning it into a pointer that 'exists forever' safely.
You don't have to use a global variable for the value, but you will leak memory every time you call otherwise.
Notice as well that this code is not thread safe; you'll need a mutex guard as well to achieve that, but for the simple case it's not a big deal.
Then there's nothing wrong with that, except that's a set of allocations per-call, which is a bit of a slow waste if you're doing it often.
libc::malloc and the free function used by a C library may be linked to different allocators.
Entirely true. It only works if the c program is using the standard allocator (which is guaranteed to be what libc:malloc uses); the point I was making is that using free() from C on a boxed value is undefined behaviour (and almost certainly a segfault, since rust uses jemalloc).