| # 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. |