-2

I want to create instances of Rust structs through C++. C++ has the main function and the Rust code is used as a library.

To achieve that, I need a way to store the instances in a list of some kind and return the index of the list to C++ so it works like a pointer to a Rust struct.

Rust does not support static members, so I cannot create a static rust_instances: std::vec::Vec = std::vec::Vec::new() to hold the Rust structures.

What would be the best option here?

I've searched and found some workarounds that simulate a static element, but I'd like to know if there's a better way to solve this problem.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
PPP
  • 1,279
  • 1
  • 28
  • 71
  • Do you have a C or C++ interface for your Rust code? If so, can't you just call that? – tadman Jun 12 '20 at 06:53
  • There is stuff like [`lazy_static`](https://docs.rs/lazy_static/1.4.0/lazy_static/) that can do things like this, too, inside of Rust. – tadman Jun 12 '20 at 06:55
  • @tadman my Rust code has a C interface. But what if I want to create multiple struct instances on Rust? Where do I store them? The main function is in the C++ side so Rust needs to store things statically – PPP Jun 12 '20 at 07:50
  • 1
    *The main function is in the C++ side so Rust needs to store things statically* - this makes no sense to me. Why exactly does using Rust from C++ mean you need to store things in static memory instead of using the stack and heap(s)? – trent Jun 12 '20 at 11:31

1 Answers1

2

To achieve that, we need a way to store the instances into a list of some kind, and return the index of the list to C++, so it works like a pointer to a Rust struct.

I can't see why this would be the case. You don't need a static list to return pointers from Rust. Simply allocate a Box in Rust and return it to the C++ code – a Box<T> with T: Sized has the same memory layout as a C pointer.

As explained in the linked documentation, your code can simply look like this:

// C++ header

// Returns ownership to the caller
extern "C" void *foo_new();

// Borrows mutably. The pointee cannot be changed by a different thread
// during the runtime of the function. The argument must be a pointer
// allocated with foo_new().
extern "C" void foo_transmogrify(void *);

// Takes ownership from the caller; no-op when invoked with NULL
extern "C" void foo_delete(void *);
#[repr(C)]
pub struct Foo {
    glonk: bool,
}

#[no_mangle]
pub extern "C" fn foo_new() -> Box<Foo> {
    Box::new(Foo { glonk: false })
}

#[no_mangle]
pub extern "C" fn foo_transmogrify(foo: &mut Foo) {
    foo.glonk = true;
}

#[no_mangle]
pub extern "C" fn foo_delete(_: Option<Box<Foo>>) {}

Note that the deallocation function can simply be empty. It will take ownership of the Box and implicitly drops it at the end of the function body.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • If I understood correctly be reading the rust page, on the C side we're gonna have a pointer to the C struct version of the Rust struct, right? But my struct is complicated, I can't make a C version of it. Perhaps I could make a struct that holds a pointer to the original struct? And then I could have this struct in both C++ and Rust? – PPP Jun 12 '20 at 19:38
  • @LucasZanella If you can't represent it on the C side, just use an opaque pointer `void*`. You can't access fields inside the struct from C, but if you can't represent it in C, you can't do that anyway. – Sven Marnach Jun 12 '20 at 19:43
  • sorry I didnt undertand. You mean `void*` on where? I've used `void*` return type before and casted as int64 on java side. You mean return a `Box` on Rust but on the C++ side expecting it to be void*? Then passing this `void*` to Rust and receiving as `uint64` on Rust again to call methods on it? – PPP Jun 12 '20 at 19:54
  • @LucasZanella In the C++ snippet above, you can replace `Foo` with `void`, and it should just work. In other functions that are supposed to operate on a `Foo` on the Rust side, you can use `void *` on the C++ side and `&Foo` or `&mut Foo` on the Rust side – Sven Marnach Jun 12 '20 at 20:07
  • 1
    @LucasZanella In C, instead of `void`, you may use an opaque struct (e.g. `struct foo;`) which is simply never fully defined anywhere, and is only used behind a pointer. I refer you to the [FFI Omnibus](http://jakegoulding.com/rust-ffi-omnibus/objects/) (note that `struct zip_code_database` in the C listing is not defined). I am pretty sure it works the same way in C++ since that is also what the ["PImpl" technique](https://en.cppreference.com/w/cpp/language/pimpl) is based on. – trent Jun 12 '20 at 20:26
  • @SvenMarnach does it really need to be received as `&mut Foo` or `&Foo`? Can't I pass the void by value when needed? And receive in Rust as `Foo`? – PPP Jun 14 '20 at 07:10
  • Why would you want to receive a `Foo`? It would mean that the Rust function takes ownership, and drops the value at the end of the function. – Sven Marnach Jun 18 '20 at 08:34
  • Are you sure that `foo_delete` should receive an option to box? Can't make it work here, getting undefined reference – PPP Jun 30 '20 at 05:04
  • @LucasZanella I didn't try it myself, but that's what [the documentation I linked](https://doc.rust-lang.org/beta/std/boxed/index.html#memory-layout) says. "Undefined reference" sounds like a linker error, so I don't think it's related to your function signatures, but I don't think we will be able to debug this problem in the comments here. – Sven Marnach Jun 30 '20 at 12:59