So you seem to be both exporting FFI functions and calling them from within Rust itself, is that right?
If so, the better pattern is to have Rust functions as usual,
Example
fn get_fancy_string ()
-> String
{
String::from("Hello, World!");
}
and then have a wrapping function / a shim over the Rust function for FFI:
mod exported {
use ::core::{ptr, os::raw::c_char};
#[no_mangle] pub extern "C"
fn get_fancy_string ()
-> *mut c_char
{
CString::new(super::get_fancy_string())
.map(CString::into_raw)
.unwrap_or_else(|err| {
eprintln!("Error, `get_fancy_string()` returned a string with inner null bytes: {}", err);
ptr::null_mut()
}
}
#[no_mangle] pub unsafe extern "C"
fn free_rust_string (p: *mut c_char)
{
if p.is_null() { return; }
drop::<CString>(CString::from_raw(p));
}
}
so that internally you can still be calling get_fancy_string()
.
- If this is cross-crate, it still works by having the crate exporting functions be not only a
cdylib
but also an rlib
, so as to depend on it as you depend on any Rust crate.
Now, assuming the above suggestion is not applicable, then indeed you'd have to go through the C ABI you have defined.
So, my rule of thumb would be:
Use CStr::from_ptr
to borrow the resulting string, and then get you own owned version.
So that leans towards your "Code 2" suggestion, although you can optimize it a bit with your own newtype expressing the special free it has:
use ::std::{
ffi:{CStr, CString},
ptr,
ops::Deref,
};
fn get_fancy_string ()
-> Option<impl 'static + Deref<Target = str>>
{
unsafe {
let ptr = ptr::NonNull::new(get_name_ffi_call())?;
let c_str = CStr::from_ptr(ptr.as_ptr());
let str = if let Ok(it) = c_str.to_str() { it } else {
free_name_ffi_call(ptr);
return None;
};
return Some(BoxedStr( // all this is just type-level stuff, in practice it's a no-op
ptr::NonNull::from(str)
));
}
// where
struct BoxedStr /* = */ (
ptr::NonNull<str>, // `&'a str` but without the annoying lifetime parameter
);
impl Deref for BoxedStr {
type Target = str;
#[inline]
fn deref (self: &'_ Self) -> &'_ str
{
self.0.as_ref()
}
}
impl Drop for BoxedStr {
fn drop (self: &'_ mut Self)
{ unsafe {
free_name_ffi_call(self.as_ptr())
}}
}
// The following assumes the FFI crate is not doing crazy things
unsafe impl Send for BoxedStr where Box<str> : Send {}
unsafe impl Sync for BoxedStr where Box<str> : Sync {}
}
- The idea is that you construct your own
Box<str>
-like abstraction thanks to having access to the free
function from FFI: instead of cloning + dropping the obtained thing, you just keep a handle on the obtained here but with this abstraction layer that ensures it gets freed with the special FFI function.
Aside: an alternative to an ownership-based API in FFI
The solution suggested by the omnibus is mainly the simplest and most intuitive one, but resorting to heap allocations just to be able to provide to the FFI a pointer is suboptimal. For some structures, a stack pointer would suffice. But given that the stack of the FFI function is cleaned up when returning to you, the only way to get that working is through callbacks:
/// Imagine it is an opaque object,
/// so the size / alignment is not guaranteed for FFI,
/// hence the need to always work with `*mut Foo` at the ABI-level.
pub
struct Foo
where
Self : Sized, // We do know that it is not a DST, though
{
x: i32,
}
#[no_mangle] pub unsafe extern "C"
fn foo_get_x (foo: *const Foo)
-> i32
{
let foo = &*foo;
foo.x
}
/* == Classic pattern == */
#[no_mangle] pub extern "C"
fn new_foo ()
-> Option<Box<Foo>> // (could also be using Arc instead of Box)
// or
// -> *mut Foo
{
Some(Box::new(Foo::new()))
// or
// Box::into_raw(Box::new(Foo::new()))
}
#[no_mangle] pub extern "C"
fn free_foo (_: Option<Box<Foo>>)
{}
// or
#[no_mangle] pub unsafe extern "C"
fn free_foo (p: *mut Foo)
{
if p.is_null() { return; }
drop::<Box<Foo>>(Box::from_raw(p))
}
/* == End of classic pattern == */
// The above pattern requires heap-allocating, just to provide a pointer to a Foo through FFI
// But if we only want to provide a borrowed access to a Foo, we can manage to stack-allocate it:
#[no_mangle] pub unsafe extern "C"
fn with_foo (
data: *mut c_void,
cb: Option<unsafe extern "C" fn (data: *mut c_void, foo: *mut Foo)>,
)
{
let cb = if let Some(cb) = cb { cb } else {
return;
};
let ref mut foo = Foo::new();
cb(data, foo);
}
which C code can then call as:
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include "rust_ffi.h"
void cb (void * data, foo_t * foo)
{
int32_t * at_x = (int32_t *) data;
*at_x = foo_get_x(foo);
}
int main (int argc, char const * const argv[])
{
int32_t x = 0;
with_foo((void *) &x, cb);
printf("foo.x = %" PRId32 "\n", x);
return 0;
}
The pattern gets very cumbersome because ANSI C has zero sugar for closures (some compiler extensions can greatly help in that regard), but at least:
-
there is no more worrying about how to free foo
; it "automagically" happens at the end of cb
-
Rust gets to stack allocate it (e.g., imagine stack-allocating a small C string).