Your C object looks a lot like an Rc<...>
, so the Rustic thing to do is to sort of mimic the interface thereof.
I am assuming that your C library copies the string when set and returns a reference on get. This is the only option that is trivially thread-safe and leak-free; copying on get would either leak memory or violate thread safety (if it copied into a static buffer), while not copying on set would require the embedder to manage name memory, which is often considered antisocial, especially since foo_set_name
does not return the old pointer, so the caller cannot free it without tracking. If your library copies in a different combination, you will need to adjust the example.
#![feature(unsafe_no_drop_flag)]
#![feature(libc)]
use std::ptr;
use std::ffi::{CStr,CString};
extern crate libc;
use libc::{c_void,c_char};
extern {
fn foo_create() -> *mut c_void;
fn foo_addref(foo: *mut c_void);
fn foo_release(foo: *mut c_void);
fn foo_get_name(foo: *mut c_void) -> *mut c_char;
fn foo_set_name(foo: *mut c_void, name: *mut c_char);
}
// optional, saves a word at the expense of allowing drop to be called twice
#[unsafe_no_drop_flag]
// type-safe wrapper
struct Foo(*mut c_void);
impl Drop for Foo {
fn drop(&mut self) {
if self.0.is_null() { return; } // only needed with unsafe_no_drop_flag
unsafe { foo_release(self.0); }
self.0 = ptr::null_mut(); // unsafe_no_drop_flag
}
}
impl Clone for Foo {
fn clone(&self) -> Foo {
unsafe { foo_addref(self.0) };
Foo(self.0)
}
}
impl Foo {
fn new() -> Foo {
unsafe { Foo(foo_create()) }
}
// &mut self would not be useful because we have rc-semantics
// &mut really means "unique" and with the clone above, there can be many
// references even if the pointer seems unique
fn set_name(&self, name: &CStr) {
// We are assuming that your library copies the name on set - see above
unsafe { foo_set_name(self.0, name.as_ptr() as *mut c_char); }
}
fn get_name(&self) -> CString {
// The to_owned copies the string. We have to do this, because someone might set the name
// immediately afterward. It's not good enough to use &mut, because of RC cloning.
// We don't have to worry about threads because the `Foo` type lacks the `Send` and `Sync`
// traits, so it cannot be shared between threads. The interface you gave does not support
// setting the name from multiple threads anyway (what would the lifetime of the get_name
// return be?)
unsafe { CStr::from_ptr(foo_get_name(self.0)).to_owned() }
}
}
Incidentally, you can avoid most refcount operations by passing &Foo
around to functions instead of cloning a new Foo
for every call.
Now, you complained (I think) about not being able to access the name without copying. The reason we need to copy the name is that we can't guarantee nobody will write the name within a scope; but we could make that guarantee with additional kinds of pointer:
// Invariant: a UniqueFoo wraps a Foo with a reference count of 1
// thus, we do _not_ derive(Clone)
struct UniqueFoo(Foo);
impl UniqueFoo {
fn new() -> UniqueFoo {
UniqueFoo(Foo::new())
}
// Here it _is_ useful to use &mut, because we guarantee that
// UniqueFoo cannot alias inside itself, but we still need to avoid aliased
// refs _to_ UniqueFoo.
fn set_name(&mut self, name: &CStr) {
self.0.set_name(name);
}
fn get_name(&self) -> &CStr {
// The borrow checker prevents any other access to this UniqueFoo
// while the CStr lives (CStr has an inferred lifetime argument), and
// our invariants prevent any access from other Foo instances, so
// the string will live as long as it needs to!
unsafe { CStr::from_ptr(foo_get_name(self.0 .0)) }
}
// Once you've done this, you can clone it, so we can never enforce our invariant again.
// Take self by value - there's no going back.
fn into_rc(self) -> Foo {
self.0
}
}
You could get significantly fancier if you needed to, e.g. clonable immutable references that could be read from but not coexist with writers.