Skip to content

Commit fac88ac

Browse files
ssbrcopybara-github
authored andcommitted
Quick first draft of a Crubit cookbook for C++ libraries.
We've discussed a number of these before, but the actual collection into a document has constantly been put on hold... :) PiperOrigin-RevId: 696971987 Change-Id: I89a146645960c7e59a90312498a17d7de390c03a
1 parent 9bb2747 commit fac88ac

File tree

3 files changed

+206
-1
lines changed

3 files changed

+206
-1
lines changed

bazel/llvm.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def _llvm_loader_repository(repository_ctx):
5353
executable = False,
5454
)
5555

56-
LLVM_COMMIT_SHA = "03730cdd3d10c5270fe436777a37d50b0838a3bf"
56+
LLVM_COMMIT_SHA = "b3134fa2338388adf8cfb2d77339d0b042eab9f6"
5757

5858
def llvm_loader_repository_dependencies():
5959
# This *declares* the dependency, but it won't actually be *downloaded* unless it's used.

docs/cpp/cookbook.md

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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+
```

docs/sitemap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
* Rust Bindings for C++ Libraries
1212

1313
* [Overview](/docs/cpp/)
14+
* [Cookbook](/docs/cpp/cookbook)
15+
* <hr>
1416
* [Functions](/docs/cpp/functions)
1517
* [Classes and Structs](/docs/cpp/classes_and_structs)
1618
* [Enums](/docs/cpp/enums)

0 commit comments

Comments
 (0)