C++
shared_ptr<Foo> create_foo();
Rust
extern "C" {
pub fn create_foo() -> ???;
}
Bindgen turns a shared_ptr
into an opaque blob.
I can't just take the raw pointer because then the C++ code is unaware that I have a reference to Foo
and might call its deconstructor.
Rust can link to/call C functions via its FFI, but not C++ functions. While I don't know why you can't call C++ functions, it is probably because C++ functions are complicated. You can just define C linkage on any C++ function, making it available from C and thus also Rust. extern "C" is your friend here.
std::shared_ptr is a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr objects may own the same object.
All the instances point to the same object, and share access to one "control block" that increments and decrements the reference count whenever a new shared_ptr is added, goes out of scope, or is reset. When the reference count reaches zero, the control block deletes the memory resource and itself.
std::shared_ptr
is a C++ class and a non-trivial type that can not be exported as is from a library — you need its definition in your target language to conform to the one in C++. To use FFI you need to provide your library functions with a simple C ABI (the C++ ABI is not stable and may change between compiler versions (as might Rust's ABI)) and I doubt that all of functions related to std::shared_ptr
are such, so there is one more obstacle for that.
I'd suggest to return a raw C-pointer from your library instead and own it in Rust.
Even in C++, to load a C++ library you provide C-ABI functions (via extern C
) to gain access to a pointer of your type and then you use it in C++ as how as you want.
So, a few points:
Return a raw C pointer from a function without name mangling so that we know its name and can link to it:
extern "C" Foo* create_foo();
Add a deleter which knows how to properly deallocate the object:
extern "C" void delete_foo(Foo *);
Let the library user (Rust) decide how to own it, for example, by boxing
the value and using it with atomic reference counter via std::sync::Arc
(as std::shared_ptr
does):
extern "C" {
fn create_foo() -> *mut Foo;
fn delete_foo(p: *mut Foo);
}
struct MyFoo {
raw: *mut Foo,
}
impl MyFoo {
fn new() -> MyFoo {
unsafe { MyFoo { raw: create_foo() } }
}
}
impl Drop for MyFoo {
fn drop(&mut self) {
unsafe {
delete_foo(self.raw);
}
}
}
fn main() {
use std::sync::Arc;
let value = Arc::new(MyFoo::new());
let another_value = value.clone();
println!("Shared counter: {}", Arc::strong_count(&value));
}
Let the C++ side forget about owning this pointer - you can't rely on it if it is used from outside the library and you give a raw pointer to it.
If you don't have any access to the library sources, can't do anything with it: the std::shared_ptr
object will not release the pointer ever and we can't make it not to delete the pointer.
I can't just take the raw pointer because then the C++ code is unaware that I have a reference to
Foo
and might calls it's deconstructor.
Yes and no. With your actual example. C++ will give ownership of the shared_ptr
to the one who called create_foo
, so C++ knows that there is still something that owns the pointer.
You need to add a get
function that will get the value for you without losing ownership of the pointer, something like this:
extern "C" {
std::shared_ptr<Foo> create_foo() {
// do the thing
}
/* or maybe this
std::shared_ptr<Foo> &&create_foo() {
// do the thing
}
*/
Foo *get_foo(std::shared_ptr<Foo> &foo) {
foo.get();
}
void destroy_foo(std::shared_ptr<Foo> foo) {
}
/* or maybe this
void destroy_foo(std::shared_ptr<Foo> &&foo) {
}
*/
}
Also shared_ptr<Foo>
is not valid C, so I don't know if bindgen and C++ with accept this (probably a warning) but that is already present in your code.
On the Rust side, you could do this:
// must be generated by bindgen and this might create a lot of problems
// this need to be the same struct as the shared_ptr on the C++ side.
// if even one octet is not correct you will run into bugs
// BE SURE that bindgen don't implement Copy for this
struct shared_ptr<T>;
struct Foo(i32);
extern "C" {
fn create_foo() -> shared_ptr<Foo>;
fn get_foo(foo: &shared_ptr<Foo>) -> *mut Foo;
fn destroy_foo(foo: shared_ptr<Foo>);
}
fn main() {
unsafe {
let my_shared_foo = create_foo();
let foo = get_foo(&my_shared_foo);
(*foo).0;
destroy_foo(my_shared_foo);
}
}
Of course this is just an example, and nothing is really safe in any of this. And as I can't test, please let me know if I wrote something that doesn't work. bindgen
should do the job.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With