| # 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](#opaque_fields). |
| |
| To have bindings, the class must be ["Rust-movable"](#rust_movable). For |
| example, any trivial or "POD" class is Rust-movable. |
| |
| ## Example |
| |
| Given the following C++ header: |
| |
| ```live-snippet |
| cs/file:examples/cpp/trivial_struct/example.h class:Position |
| ``` |
| |
| Crubit will generate a struct with the same layout: |
| |
| ```live-snippet |
| 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/](http://examples/cpp/trivial_abi_struct/). |
| |
| ## Fields {#fields} |
| |
| The fields on the Rust struct type are the corresponding Rust types: |
| |
| * If the C++ field has [primitive type](../types/primitive.md), then the Rust |
| field uses the corresponding Rust type. |
| * Similarly, if the C++ field has [pointer type](../types/pointer.md), then |
| the Rust field has the corresponding Rust pointer type. |
| * If the field has a user-defined type, such as a |
| [class type](classes_and_structs.md) or [enum](enums.md), then the bindings |
| for the function use the bindings for that type. |
| |
| ### Unsupported fields {#opaque_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-`public` fields (`private` or `protected` fields) |
| * 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](../types/pointer.md#function), but can otherwise |
| be used as normal. |
| |
| <span id="trivially_relocatable"></span> |
| |
| ## Rust-movable classes {#rust_movable} |
| |
| 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: |
| |
| ```c++ |
| 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`](https://github.com/abseil/abseil-cpp/blob/master/absl/base/attributes.h#:~:text=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](https://en.cppreference.com/w/cpp/language/classes#Trivially_copyable_clas) |
| type is also Rust-movable. |
| * Any `class` or `struct` type with only Rust-movable fields and base classes |
| is Rust-movable, unless: |
| * it is not `ABSL_ATTRIBUTE_TRIVIAL_ABI` and defines a copy/move |
| constructor, copy/move assignment operator, or destructor, or, |
| * it is otherwise nontrivial, e.g., from defining a `virtual` member |
| function. |
| |
| Some examples of Rust-movable types: |
| |
| * any primitive type (integers, character types, floats, etc.) |
| * raw pointers |
| * `string_view` |
| * [`struct tm`](https://en.cppreference.com/w/cpp/chrono/c/tm), or any other |
| type in the C standard library |
| * `unique_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 {#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. |