In my ongoing saga of writing a safe wrapper for the Cassandra C++ driver, my eye now turns towards avoiding memory leaks when calling C functions with signatures like:
The Cassandra C API for cass_string_init2 looks like:
Note: This does not allocate memory. The object wraps the pointer passed into this function.
CASS_EXPORT CassString cass_string_init2(const char* data, cass_size_t length);
That is, it takes a string, and returns an alternate representation of a string. That representation looks like:
typedef struct CassString_ {
const char* data;
cass_size_t length;
} CassString;
This is where you want to use #[repr(C)]
in the Rust code:
#[repr(C)]
struct CassStr {
data: *const c_char,
length: size_t,
}
The nicest thing you could do is make strings automatically convert to this struct:
trait AsCassStr {
fn as_cass_str(&self) -> CassStr;
}
impl AsCassStr for str {
fn as_cass_str(&self) -> CassStr {
CassStr {
data: self.as_bytes(),
length: self.len(),
}
}
}
And then have your API accept anything that implements AsCassStr
. This allows you to have owned variants as well. You may also want to look into PhantomData
to allow enforcing lifetimes of the CassStr
object.
Note Normally you want to use CString
to avoid strings with interior NUL bytes. However, since the API accepts a length parameter, it's possible that it natively supports them. You'll need to experiment to find out. If not, then you'll need to use CString
as shown below.
Lets take a look at your function, line-by-line:
impl AsContactPoints for str {
fn as_contact_points(&self) -> ContactPoints {
let cstr = CString::new(self).unwrap(); // 1
let bytes = cstr.as_bytes_with_nul(); // 2
let ptr = bytes.as_ptr(); // 3
ContactPoints(ptr as *const i8) // 4
} // 5
}
CString
. This allocates a bit of memory somewhere, verifies that the string has no internal NUL bytes, then copies our string in, byte-for-byte, and adds a trailing zero.cstr
is a local variable, and so the bytes it is holding are likewise freed. You now have a dangling pointer. Not good!You need to ensure that the CString
lives for as long as it needs to. Hopefully the function you are calling doesn't keep a reference to it, but there's no easy way to tell from the function signature. You probably want code that looks like:
fn my_cass_call(s: &str) {
let s = CString::new(s).unwrap();
cass_call(s.as_ptr()) // `s` is still alive here
}
The benefit here is that you never store the pointer in a variable of your own. Remember that raw pointers do not have lifetimes, so you have to be very careful with them!