|
| 1 | +# C++ Bindings Cookbook |
| 2 | + |
| 3 | +This document presents a collection of techniques for creating Rust bindings for |
| 4 | +C++ libraries. |
| 5 | + |
| 6 | +These techniques are often *workarounds* for gaps in what Crubit can do. Expect |
| 7 | +the recommended practices to evolve over time, as Crubit's capabilities expand! |
| 8 | + |
| 9 | +> BEST PRACTICE: The tips below describe deviations from typical C++ style. (If |
| 10 | +> typical C++ style worked, you wouldn't need a cookbook.) When you deviate from |
| 11 | +> typical C++ style, **document why**, and try to keep changes limited in scope, |
| 12 | +> close to the interop boundary. |
| 13 | +> |
| 14 | +> If possible, solve the same problem while staying within more typical C++ |
| 15 | +> style. For example: you may be able to |
| 16 | +> [add `ABSL_ATTRIBUTE_TRIVIAL_ABI`](#trivial_abi) to a type you control, |
| 17 | +> instead of [boxing the type in a pointer](#boxing). |
| 18 | +
|
| 19 | +## Making types Rust-movable |
| 20 | + |
| 21 | +As described in <internal link>/cpp/classes_and_structs#trivially_relocatable, types |
| 22 | +cannot be passed by value in Rust unless they are Rust-movable, or, in C++ |
| 23 | +terminology, trivially relocatable. |
| 24 | + |
| 25 | +This can happen for a couple of easily fixable reasons, described in |
| 26 | +subsections: |
| 27 | + |
| 28 | +* The type defines a destructor or copy/move constructor / assignment |
| 29 | + operator. If it is in-principle still trivially relocatable, and these |
| 30 | + functions do not care about the address of the object in memory, then the |
| 31 | + type can be [annotated with `ABSL_ATTRIBUTE_TRIVIAL_ABI`](#trivial_abi) |
| 32 | +* The type has a field which is not rust-movable. In that case, the field can |
| 33 | + be [boxed in a pointer](#boxing). |
| 34 | + |
| 35 | +There are *other* reasons a type can become non-trivially-relocatable, which do |
| 36 | +not have these easy fixes described below. For example, virtual methods, or |
| 37 | +non-trivially-relocatable base classes. For those, your only option is the hard |
| 38 | +option of more radically restructuring your code to avoid those patterns. |
| 39 | + |
| 40 | +### `ABSL_ATTRIBUTE_TRIVIAL_ABI` {#trivial_abi} |
| 41 | + |
| 42 | +<internal link>/cpp/cookbook#trivial_abi |
| 43 | + |
| 44 | +One of the ways a type can become non-trivially-relocatable is if it has a |
| 45 | +copy/move constructor / assignment operator, or a destructor. In that case, |
| 46 | +Clang will assume that it cannot be trivially relocated, **unless** it is |
| 47 | +annotated with `ABSL_ATTRIBUTE_TRIVIAL_ABI`. |
| 48 | + |
| 49 | +```c++ {.bad} |
| 50 | +struct LogWhenDestroyed { |
| 51 | + ~LogWhenDestroyed() { |
| 52 | + std::cerr << "I was destroyed!\n"; |
| 53 | + } |
| 54 | +}; |
| 55 | +``` |
| 56 | +
|
| 57 | +```c++ {.good} |
| 58 | +struct ABSL_ATTRIBUTE_TRIVIAL_ABI LogWhenDestroyed { |
| 59 | + ~LogWhenDestroyed() { |
| 60 | + std::cerr << "I was destroyed!\n"; |
| 61 | + } |
| 62 | +}; |
| 63 | +``` |
| 64 | + |
| 65 | +> WARNING: Only use `ABSL_ATTRIBUTE_TRIVIAL_ABI` if changing the location of an |
| 66 | +> object in memory is safe. In particular, if the object is self-referential, |
| 67 | +> using `ABSL_ATTRIBUTE_TRIVIAL_ABI` will result in Undefined Behavior (UB). |
| 68 | +> |
| 69 | +> ```c++ {.bad} |
| 70 | +> class SelfReferential { |
| 71 | +> public: |
| 72 | +> SelfReferential(const SelfReferential& other) : x(other.x), x_ptr(&x) {} |
| 73 | +> private: |
| 74 | +> int x = 0; |
| 75 | +> int* x_ptr = &x; |
| 76 | +> } |
| 77 | +> ``` |
| 78 | +> |
| 79 | +> Types like this, if Rust-moved, will contain invalid pointers. Carefully |
| 80 | +> review any code adding `ABSL_ATTRIBUTE_TRIVIAL_ABI`. |
| 81 | +
|
| 82 | +### Boxing in a pointer {#boxing} |
| 83 | +
|
| 84 | +<internal link>/cpp/cookbook#boxing |
| 85 | +
|
| 86 | +One of the ways a type can become non-trivially-relocatable is if it has a |
| 87 | +field, where the type of that field is not trivially relocatable. There is no |
| 88 | +way to override this: there is nothing a type can do to make itself trivially |
| 89 | +relocatable if one subobject is not. |
| 90 | +
|
| 91 | +For example, consider a field like `std::string name;`. `std::string` defines a |
| 92 | +custom destructor and copy / move constructor/assignment operator, in order to |
| 93 | +correctly manage owned heap memory for the string. Because of this, it also is |
| 94 | +not trivially relocatable / rust-movable. And, at the time of writing, |
| 95 | +`std::string` currently cannot use `ABSL_ATTRIBUTE_TRIVIAL_ABI` in any STL |
| 96 | +implementation. In the case of libstdc++, for example, `std::string` contains a |
| 97 | +self-referential pointer: when the string is small enough, the `data()` pointer |
| 98 | +refers to the inside of the string. Rust-moving it would cause the pointer to |
| 99 | +refer back to the *old* object, which would cause undefined behavior. |
| 100 | +
|
| 101 | +If a struct or class contains a `std::string` as a subobject by value, or any |
| 102 | +other non-trivially-relocatable object, then that struct or class is itself also |
| 103 | +not trivially relocatable. (If you somehow were able to Rust-move the parent |
| 104 | +object, this would also Rust-move the `string`, causing the very same issues.) |
| 105 | +
|
| 106 | +Instead, what you can do is change the type of the field, so that it doesn't |
| 107 | +contain the problematic type *by value*. Instead, it can hold the |
| 108 | +non-trivially-relocatable type by pointer. |
| 109 | +
|
| 110 | +BEST PRACTICE: Except where necessary for better Rust interop, this is **not** |
| 111 | +good C++ style. When you use this trick, document why, and try to limit it to |
| 112 | +types close to the interop boundary. If possible, instead of boxing `T`, make |
| 113 | +`T` itself rust-movable. (This is not easy for standard library types, but if |
| 114 | +the type is under your control, it *may* be as easy as adding |
| 115 | +`ABSL_ATTRIBUTE_TRIVIAL_ABI`.) |
| 116 | +
|
| 117 | +#### `unique_ptr` {#unique_ptr} |
| 118 | +
|
| 119 | +NOTE: The following is non-portable, and only works in libc++ with the unstable |
| 120 | +ABI. If you aren't sure about whether you are using the unstable ABI, it is likely that you are not, but you might want to check in with your local toolchain maintainer. |
| 121 | +
|
| 122 | +If you tightly control your dependencies, you might be using |
| 123 | +libc++'s unstable ABI. The unstable ABI, among other things, makes |
| 124 | +`unique_ptr<T>` trivially relocatable (in C++) and Rust-movable (in Rust). In |
| 125 | +fact, it is trivially relocatable even if `T` itself is not. |
| 126 | +
|
| 127 | +This means that if a particular field is making its parent type |
| 128 | +non-trivially-relocatable, one fix is to wrap it in a `unique_ptr`: |
| 129 | +
|
| 130 | +```c++ {.bad} |
| 131 | +struct Person { |
| 132 | + std::string name; |
| 133 | + int age; |
| 134 | +} |
| 135 | +``` |
| 136 | +
|
| 137 | +```c++ {.good} |
| 138 | +struct Person { |
| 139 | + // boxed to make Person rust-movable: <internal link>/cpp/cookbook#boxing |
| 140 | + std::unique_ptr<std::string> name; |
| 141 | + int age; |
| 142 | +} |
| 143 | +``` |
| 144 | +
|
| 145 | +#### Raw pointers {#raw_ptr} |
| 146 | +
|
| 147 | +BEST PRACTICE: This should only be used in codebases that do not use a trivially |
| 148 | +relocatable `unique_ptr` or `unique_ptr` equivalent. Consider wrapping this in |
| 149 | +an `ABSL_ATTRIBUTE_TRIVIAL_ABI` type which resembles `unique_ptr`, instead. |
| 150 | +
|
| 151 | +<section class="zippy" markdown="1"> |
| 152 | +
|
| 153 | +When not using libc++'s unstable ABI, the most straightforward way to make a |
| 154 | +field trivially relocatable is to instead use a **raw** pointer, and delete it |
| 155 | +in the destructor (as if it were held by a `unique_ptr`). |
| 156 | +
|
| 157 | +```c++ {.bad} |
| 158 | +struct Person { |
| 159 | + std::string name; |
| 160 | + int age; |
| 161 | +} |
| 162 | +``` |
| 163 | +
|
| 164 | +```c++ {.good} |
| 165 | +struct ABSL_ATTRIBUTE_TRIVIAL_ABI Person { |
| 166 | + // Owned, boxed to make Person rust-movable: <internal link>/cpp/cookbook#boxing |
| 167 | + std::string* name; |
| 168 | + int age; |
| 169 | +
|
| 170 | + ~Person() { |
| 171 | + delete name; |
| 172 | + } |
| 173 | +} |
| 174 | +``` |
| 175 | +
|
| 176 | +(Note the use of `ABSL_ATTRIBUTE_TRIVIAL_ABI`: because we added a destructor, we |
| 177 | +also need to add `ABSL_ATTRIBUTE_TRIVIAL_ABI` to indicate that the destructor |
| 178 | +does not care about the address of `Person`.) |
| 179 | +
|
| 180 | +</section> |
| 181 | +
|
| 182 | +## Renaming functions for Rust {#renaming} |
| 183 | +
|
| 184 | +<internal link>/cpp/cookbook#renaming |
| 185 | +
|
| 186 | +Overloaded functions cannot be called from Rust (yet: b/213280424). To make them |
| 187 | +available anyway, you can define new non-overloaded functions with different |
| 188 | +names: |
| 189 | +
|
| 190 | +```c++ {.bad} |
| 191 | +void Foo(int x); |
| 192 | +void Foo(float x); |
| 193 | +``` |
| 194 | +
|
| 195 | +```c++ {.good} |
| 196 | +void Foo(int x); |
| 197 | +void Foo(float x); |
| 198 | +
|
| 199 | +// For Rust callers: <internal link>/cpp/cookbook#renaming |
| 200 | +inline void FooInt(int x) {return Foo(x);} |
| 201 | +// For Rust callers: <internal link>/cpp/cookbook#renaming |
| 202 | +inline void FooFloat(float x) {return FooFloat(x);} |
| 203 | +``` |
0 commit comments