• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1{{#title Other Rust–C++ interop tools — Rust ♡ C++}}
2# Context: other Rust–C++ interop tools
3
4When it comes to interacting with an idiomatic Rust API or idiomatic C++ API
5from the other language, the generally applicable approaches outside of the CXX
6crate are:
7
8- Build a C-compatible wrapper around the code (expressed using `extern "C"`
9  signatures, primitives, C-compatible structs, raw pointers). Translate that
10  manually to equivalent `extern "C"` declarations in the other language and
11  keep them in sync. Preferably, build a safe/idiomatic wrapper around the
12  translated `extern "C"` signatures for callers to use.
13
14- Build a C wrapper around the C++ code and use **[bindgen]** to translate that
15  programmatically to `extern "C"` Rust signatures. Preferably, build a
16  safe/idiomatic Rust wrapper on top.
17
18- Build a C-compatible Rust wrapper around the Rust code and use **[cbindgen]**
19  to translate that programmatically to an `extern "C"` C++ header. Preferably,
20  build an idiomatic C++ wrapper.
21
22**If the code you are binding is already *"effectively C"*, the above has you
23covered.** You should use bindgen or cbindgen, or manually translated C
24signatures if there aren't too many and they seldom change.
25
26[bindgen]: https://github.com/rust-lang/rust-bindgen
27[cbindgen]: https://github.com/eqrion/cbindgen
28
29## C++ vs C
30
31Bindgen has some basic support for C++. It can reason about classes, member
32functions, and the layout of templated types. However, everything it does
33related to C++ is best-effort only. Bindgen starts from a point of wanting to
34generate declarations for everything, so any C++ detail that it hasn't
35implemented will cause a crash if you are lucky ([bindgen#388]) or more likely
36silently emit an incompatible signature ([bindgen#380], [bindgen#607],
37[bindgen#652], [bindgen#778], [bindgen#1194]) which will do arbitrary
38memory-unsafe things at runtime whenever called.
39
40[bindgen#388]: https://github.com/rust-lang/rust-bindgen/issues/388
41[bindgen#380]: https://github.com/rust-lang/rust-bindgen/issues/380
42[bindgen#607]: https://github.com/rust-lang/rust-bindgen/issues/607
43[bindgen#652]: https://github.com/rust-lang/rust-bindgen/issues/652
44[bindgen#778]: https://github.com/rust-lang/rust-bindgen/issues/778
45[bindgen#1194]: https://github.com/rust-lang/rust-bindgen/issues/1194
46
47Thus using bindgen correctly requires not just juggling all your pointers
48correctly at the language boundary, but also understanding ABI details and their
49workarounds and reliably applying them. For example, the programmer will
50discover that their program sometimes segfaults if they call a function that
51returns std::unique\_ptr\<T\> through bindgen. Why? Because unique\_ptr, despite
52being "just a pointer", has a different ABI than a pointer or a C struct
53containing a pointer ([bindgen#778]) and is not directly expressible in Rust.
54Bindgen emitted something that *looks* reasonable and you will have a hell of a
55time in gdb working out what went wrong. Eventually people learn to avoid
56anything involving a non-trivial copy constructor, destructor, or inheritance,
57and instead stick to raw pointers and primitives and trivial structs only
58&mdash; in other words C.
59
60## Geometric intuition for why there is so much opportunity for improvement
61
62The CXX project attempts a different approach to C++ FFI.
63
64Imagine Rust and C and C++ as three vertices of a scalene triangle, with length
65of the edges being related to similarity of the languages when it comes to
66library design.
67
68The most similar pair (the shortest edge) is Rust&ndash;C++. These languages
69have largely compatible concepts of things like ownership, vectors, strings,
70fallibility, etc that translate clearly from signatures in either language to
71signatures in the other language.
72
73When we make a binding for an idiomatic C++ API using bindgen, and we fall down
74to raw pointers and primitives and trivial structs as described above, what we
75are really doing is coding the two longest edges of the triangle: getting from
76C++ down to C, and C back up to Rust. The Rust&ndash;C edge always involves a
77great deal of `unsafe` code, and the C++&ndash;C edge similarly requires care
78just for basic memory safety. Something as basic as "how do I pass ownership of
79a string to the other language?" becomes a strap-yourself-in moment,
80particularly for someone not already an expert in one or both sides.
81
82You should think of the `cxx` crate as being the midpoint of the Rust&ndash;C++
83edge. Rather than coding the two long edges, you will code half the short edge
84in Rust and half the short edge in C++, in both cases with the library playing
85to the strengths of the Rust type system *and* the C++ type system to help
86assure correctness.
87
88If you've already been through the tutorial in the previous chapter, take a
89moment to appreciate that the C++ side *really* looks like we are just writing
90C++ and the Rust side *really* looks like we are just writing Rust. Anything you
91could do wrong in Rust, and almost anything you could reasonably do wrong in
92C++, will be caught by the compiler. This highlights that we are on the "short
93edge of the triangle".
94
95But it all still boils down to the same things: it's still FFI from one piece of
96native code to another, nothing is getting serialized or allocated or
97runtime-checked in between.
98
99## Role of CXX
100
101The role of CXX is to capture the language boundary with more fidelity than what
102`extern "C"` is able to represent. You can think of CXX as being a replacement
103for `extern "C"` in a sense.
104
105From this perspective, CXX is a lower level tool than the bindgens. Just as
106bindgen and cbindgen are built on top of `extern "C"`, it makes sense to think
107about higher level tools built on top of CXX. Such a tool might consume a C++
108header and/or Rust module (and/or IDL like Thrift) and emit the corresponding
109safe cxx::bridge language boundary, leveraging CXX's static analysis and
110underlying implementation of that boundary. We are beginning to see this space
111explored by the [autocxx] tool, though nothing yet ready for broad use in the
112way that CXX on its own is.
113
114[autocxx]: https://github.com/google/autocxx
115
116But note in other ways CXX is higher level than the bindgens, with rich support
117for common standard library types. CXX's types serve as an intuitive vocabulary
118for designing a good boundary between components in different languages.
119