Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrapping a Rust struct in a C++ class

Tags:

rust

I would like to wrap a Rust struct in a C++ class.

Rust:

#[repr(C)]
pub struct RustStruct {
  num: i32,
  // other members..
}

pub extern "C" fn update(rust_struct: *mut RustStruct) {
  (*rust_struct).num = 1i32;
}

extern "C" {
  void update(void*);
}

C++:

class Wrapper {
  public:
    Wrapper();
    // ..

  private:
    void* rustStruct;
    // ..
};

Wrapper::Wrapper() {
  update(rustStruct); // crash
}

int main() {
  std::cout << "Testing..";
}

I understand why this wouldn't work. My question is: how can I achieve what I'm basically trying to do (wrap a rust struct in a c++ class)?

like image 756
goo Avatar asked Aug 24 '15 17:08

goo


People also ask

Are Rust structs classes?

In languages like C, Go, and Rust, classes are not a feature. Instead, these languages use structs, which define only a group of properties. While structs don't allow you to define methods, both Rust and Go define functions in a way that provides access to structs.

Can I put struct in class in CPP?

The C++ class is an extension of the C language structure. Because the only difference between a structure and a class is that structure members have public access by default and class members have private access by default, you can use the keywords class or struct to define equivalent classes.

How to return a struct Rust?

We can return a struct from a function by specifying the struct name as the return type. We can define functions that are specific to a struct, called methods, that can only be used by instances of that struct. A method must be defined inside a impl struct_name code block.

How do you define a struct in Rust?

To define a struct, we enter the keyword struct and name the entire struct. A struct's name should describe the significance of the pieces of data being grouped together. Then, inside curly brackets, we define the names and types of the pieces of data, which we call fields.


1 Answers

There is a mix of multiple FFIs concepts in your answer, so first let me recommend that your read the Reference.

There are two ways to achieve what you wish, you can either:

  • use a POD struct (Plain Old Data), aka C-compatible struct
  • use an opaque pointer (void* in C)

Mixing them, as you did, does not make sense.


Which to pick?

Both solutions have advantages and disadvantages, it's basically an expressiveness versus performance trade-off.

On the one hand, opaque pointers are more expressive: they can point to any Rust type. However:

  • they require dynamic memory allocation
  • they require being manipulated by Rust functions (so always indirectly from C or C++)

On the other hand, POD struct do not require either of those, but they are limited to only a subset of types expressible in Rust.


How to use a POD?

This is the easiest, actually, so let's start with it!

In Rust:

#[repr(C)]
pub struct RustStruct {
    num: i32,
    // other members, also PODs!
}

In C++

struct RustStruct {
    int32_t num;
    // other members, also with Standard Layout
    // http://en.cppreference.com/w/cpp/types/is_standard_layout
};

class Wrapper {
public:
private:
    RustStruct rustStruct;
};

Note that I just got along with your question stricto censu here, you could actually merge the two in a single C++ class:

class RustStruct {
public:
private:
    int32_t num;
    // other members, also with Standard Layout
    // http://en.cppreference.com/w/cpp/types/is_standard_layout
};

Just avoid virtual methods.


How to use an opaque pointer?

This gets trickier:

  • Only the Rust code may correctly create/copy/destruct the type
  • Beware of leaking...

So, we need to implement a lot of functions in Rust:

#![feature(box_raw, box_syntax)]
use std::boxed;

pub struct RustStruct {
    num: i32,
    // other members, anything goes
}

pub extern "C" fn createRustStruct() -> *mut RustStruct {
    boxed::into_raw(box RustStruct::new())
}

pub extern "C" fn destroyRustStruct(o: *mut RustStruct) {
    boxed::from_raw(o);
}

Alright... now on to C++:

struct RustStruct;

RustStruct* createRustStruct();
void destroyRustStruct(RustStruct*);

class Wrapper {
public:
    Wrapper(): rustStruct(RustStructPtr(createRustStruct())) {}

private:
    struct Deleter {
        void operator()(RustStruct* rs) const {
            destroyRustStruct(rs);
        }
    };

    typedef std::unique_ptr<RustStruct, Deleter> RustStructPtr;

    RustStructPtr rustStruct;
}; // class Wrapper

So, yes, a bit more involved, and Wrapper is not copyable either (copy has to be delegated to Rust too). Anyway, this should get you started!

Note: if you have a lot of opaque pointers to wrap, a templated C++ class taking the copy/destroy functions as template parameters could alleviate a lot of boiler plate.

like image 150
Matthieu M. Avatar answered Nov 15 '22 10:11

Matthieu M.