Rust bindings for C++ classes and structs
A C++ class or struct is mapped to a Rust struct with the same fields. If
any subobject of the class cannot be represented in Rust, the class itself will
still have bindings, but
the relevant subobject will be private.
To have bindings, the class must be "Rust-movable". For example, any trivial or "POD" class is Rust-movable.
Example
Given the following C++ header:
cs/file:examples/cpp/trivial_struct/example.h class:Position
Crubit will generate a struct with the same layout:
cs/file:examples/cpp/trivial_struct/example_generated.rs class:Position
For an example of a Rust-movable class with a destructor, see examples/cpp/trivial_abi_struct/.
Fields
The fields on the Rust struct type are the corresponding Rust types:
- If the C++ field has primitive type, then the Rust field uses the corresponding Rust type.
- Similarly, if the C++ field has pointer type, then the Rust field has the corresponding Rust pointer type.
- If the field has a user-defined type, such as a class type or enum, then the bindings for the function use the bindings for that type.
Unsupported fields
Subobjects that do not receive bindings are made private, and replaced with an
opaque blob of [MaybeUninit<u8>; N], as well as a comment in the generated
source code explaining why the subobject could not receive bindings. For
example, since inheritance is not supported, the space of the object occupied by
a base class will instead be this opaque blob of bytes.
Specifically, the following subobjects are hidden and replaced with opaque blobs:
- Base class subobjects
- Non-
publicfields (privateorprotectedfields) - Fields that have nontrivial destructors
- Fields whose type does not have bindings
- Fields that have any unrecognized attribute, including
no_unique_address
A Rust struct with opaque blobs is ABI-incompatible with the C++ struct or class that it corresponds to. As a consequence, if the struct is used for FFI outside of Crubit, it should not be passed by value. Within Crubit, it can't be passed by value in function pointers, but can otherwise be used as normal.
Rust-movable classes
For a type to be passed or returned by value in Rust, it must be "Rust-movable":
the class must be able to be "teleported" in memory during its lifetime, as if
by using memcpy and then discarding the old location without running any
destruction logic. This means that it can be present in Rust using normal
objects and pointers and references, without using Pin.
For example, a string_view is Rust-movable. In fact, every trivially copyable
type is Rust-movable
However, unlike Rust, many types in C++ are not Rust-movable. For example, a
std::string might be implemented using the "short string optimization", in a
fashion similar to this:
class String {
union {
size_t length;
char inline_data[sizeof(length)];
};
char* data; // either points to `inline_data`, or the heap.
public:
size_t size() {
if (data == (char*)this) {
return strlen(data);
} else {
return length;
}
}
// ...
};
This class is self-referential: the data pointer may point to inline_data,
which is inside the object itself. If we bitwise copy the object to a new
location, as in a "Rust move" or as with memcpy, then the data pointer will
remain bitwise identical, and point into the old object. It becomes a
dangling pointer!
C++ allows self-referential types. In C++, fields can and often do point at
other fields, because assignment is overloadable: the assignment operator can be
modified to, when copying or moving the string, also "fix up" the data pointer
so that it points to the new location in the new object, instead of dangling.
Rust does not do this. In Rust, assignment is always a "trivial relocation" --
assignment runs no code when copying or moving an object, and copies the bytes
as they are. This would break on the String type defined above, or any other
self-referential type.
Unfortunately, any class with a user-defined copy/move operation or destructor
might be self-referential, and so by default they are not Rust-movable. If a
class has a user-defined destructor or copy/move constructor/assignment
operator, and "should be" Rust-movable, it must explicitly declare that it is
safe to perform a Rust move, using the attribute
ABSL_ATTRIBUTE_TRIVIAL_ABI.
This attribute allows a class to be trivially relocated, even though it defines
an operation that would ordinarily disable trivial relocation.
For example, in the unstable libc++ ABI we use within Google, a unique_ptr<T>
is Rust-movable, because it applies ABSL_ATTRIBUTE_TRIVIAL_ABI. This is safe
to do, for unique_ptr, because its exact location in memory does not matter,
and paired move/destroy operations can be replaced with Rust move operations.
Requirements
The exact requirements for a class to be Rust-movable are subject to change, because they are still being defined within Clang and within the C++ standard. But at the least:
- Any trivially copyable type is also Rust-movable.
- Any
classorstructtype with only Rust-movable fields and base classes is Rust-movable, unless:- it is not
ABSL_ATTRIBUTE_TRIVIAL_ABIand defines a copy/move constructor, copy/move assignment operator, or destructor, or, - it is otherwise nontrivial, e.g., from defining a
virtualmember function.
- it is not
Some examples of Rust-movable types:
- any primitive type (integers, character types, floats, etc.)
- raw pointers
string_viewstruct tm, or any other type in the C standard libraryunique_ptr, in the Clang unstable ABI.absl::Status
Some examples of types that are not Rust-movable:
- (For now)
std::string,std::vector, and other nontrivial standard library types. - (For now)
absl::flat_hash_map,absl::AnyInvocable, and other nontrivial types used throughout the C++ ecosystem, even outside the standard library. absl::Mutex,absl::Notification, and other non-movable types.
Attributes
Crubit does not support most attributes on structs and their fields. If a struct
is marked using any attribute other than alignment or
ABSL_ATTRIBUTE_TRIVIAL_ABI, it will not receive bindings. If a field is marked
using any other attribute, it will be replaced with a private opaque blob.