| # Best Practices Writing Rust Bindings for Existing C++ Libraries | |
| ## Introduction | |
| This document is an attempt at guidance for how Rust changes can be made to | |
| existing C++ libraries, including core foundational libraries. | |
| For an introduction, see [Rust Bindings for C++ Libraries](index.md). | |
| ## Code Organization {#organization} | |
| A C++ library and its Rust bindings are defined as separate targets in the same | |
| `BUILD` file: the `cc_library` target itself, and a corresponding | |
| `rust_api_from_cpp` target. | |
| ```python | |
| cc_library( | |
| name = "my_lib", | |
| hdrs = ["my_lib.h"], | |
| aspect_hints = [":my_lib_rust.hint"], | |
| ) | |
| rust_api_from_cpp( | |
| name = "my_lib_rust", | |
| cpp_target = ":my_lib", | |
| ) | |
| ``` | |
| The `rust_api_from_cpp` target acts like a `rust_library` and can be depended on | |
| by other Rust targets via `deps`. The `cc_library` rule receives an | |
| `aspect_hint` called `:my_lib_rust.hint`. This aspect hint is generated by | |
| `rust_api_from_cpp`, and tells Crubit how to generate bindings for the given | |
| `cc_library`. | |
| The `rust_api_from_cpp` target should be defined in the same `BUILD` file as the | |
| original `cc_library` target. This helps ensure that there is a single | |
| easily-discoverable Rust API for a given C++ library. | |
| ### Adding custom Rust source files (`srcs`) | |
| Handwritten Rust code can be added to the generated bindings by specifying the | |
| `srcs` attribute on `rust_api_from_cpp`: | |
| ```python | |
| rust_api_from_cpp( | |
| name = "my_lib_rust", | |
| srcs = ["my_lib_custom.rs"], | |
| cpp_target = ":my_lib", | |
| ) | |
| ``` | |
| To allow Crubit bindings to be `#![no_std]` by default, the generated crate does | |
| not include the regular `std` Rust prelude. Consequently, standard library types | |
| like `Box` have to be imported manually: | |
| ```rust | |
| extern crate std; | |
| use std::boxed::Box; | |
| ``` | |
| #### Types in `srcs` do not receive C++ bindings {#types-in-srcs-do-not-receive-cpp-bindings} | |
| Types defined in the `srcs` of a `rust_api_from_cpp` target are intended for | |
| customizing or extending the generated Rust API. However, Crubit **does not** | |
| generate C++ bindings for types defined in `srcs`. | |
| Because Crubit only generates C++ bindings for the underlying C++ target, any | |
| struct, enum, or union defined in these custom Rust source files has no C++ | |
| counterpart. If a downstream Rust crate attempts to expose a function, method, | |
| or type that uses one of these types in its public interface, Crubit cannot | |
| generate C++ bindings for that item and will report an error. | |
| If you need a type to be accessible from both C++ and Rust, you can either: | |
| * **Define the type in C++**: Define the type in the C++ library wrapped by | |
| `rust_api_from_cpp` so that Crubit generates Rust bindings for it, or | |
| * **Define the type in a separate `rust_library`**: Define the type in a | |
| separate `rust_library` target that is then used as a dependency in both | |
| Rust and (via a `cc_bindings_from_rust` target) in C++. | |
| If the type is purely for internal use within Rust and is never intended to be | |
| used from C++, you can silence the warning on the type definition using the | |
| `#[crubit_annotate::allow_unbindable_type]` attribute (or the | |
| `CRUBIT_ALLOW_UNBINDABLE_TYPE` macro in C++-accessible contexts). | |
| #### Limiting C++ function visibility to `srcs` with `CRUBIT_PUB_CRATE` {#limiting-cpp-function-visibility-to-srcs} | |
| When writing custom Rust code in `srcs` to wrap or customize a C++ API, you may | |
| need C++ helper functions or internal methods to be callable from your Rust | |
| `srcs` files, but without exposing them in the public Rust API of the generated | |
| crate. | |
| You can mark a C++ function or method with `CRUBIT_PUB_CRATE` from | |
| `"support/annotations.h"`: | |
| ```cpp | |
| #include "support/annotations.h" | |
| class MyClass { | |
| public: | |
| // Publicly available to all Rust callers: | |
| void SafeMethod(); | |
| // Internal helper: generated with `pub(crate)` visibility in Rust, | |
| // making it accessible only to custom `srcs` of this `rust_api_from_cpp`: | |
| CRUBIT_PUB_CRATE void InternalHelper(); | |
| }; | |
| // Free function limited to this crate: | |
| CRUBIT_PUB_CRATE void HelperFunction(); | |
| ``` | |
| In the generated Rust bindings, functions and methods marked with | |
| `CRUBIT_PUB_CRATE` receive `pub(crate)` visibility instead of `pub`. This allows | |
| custom Rust code in `srcs` to call them while hiding them from external crates | |
| that depend on this `rust_api_from_cpp` target. | |
| > **Note**: `CRUBIT_PUB_CRATE` can only be used on functions and methods. Using | |
| > it on types (structs, classes, enums, unions, or type aliases) results in a | |
| > compile-time error. | |
| <section class="zippy" markdown="1"> | |
| Read on only if you're curious about *why* Rust bindings targets are structured | |
| this way. | |
| #### Technical Justification | |
| Crubit generates bindings using Bazel | |
| [**aspects**](https://bazel.build/extending/aspects): given an arbitrary C++ | |
| Bazel target, Crubit generates, in an aspect, the Rust library which wraps it. | |
| This is necessary for the same reason that it's necessary for | |
| [protocol buffers](https://bazel.build/extending/aspects): to support transitive | |
| dependencies (if A depends on B, then bindings(A) must depend on bindings(B) so | |
| that bindings(A) can use types from B). | |
| However, unlike protocol buffers which use a single target for both languages, | |
| Crubit uses a separate `rust_api_from_cpp` target to represent the Rust | |
| bindings. This target is where library owners configure the bindings (for | |
| example, by adding custom Rust code which should be added to the generated | |
| target). | |
| To configure the Crubit code generation for a C++ target, we use `aspect_hints`. | |
| The `rust_api_from_cpp` macro automatically generates a `.hint` target (named | |
| `<name>.hint`) which contains the configuration. This hint target is then | |
| attached to the C++ target's `aspect_hints`. | |
| ### Example | |
| To enable Crubit on a C++ target, define a `rust_api_from_cpp` target and add | |
| its `.hint` target to the C++ target's `aspect_hints`: | |
| ```python | |
| cc_library( | |
| name = "my_lib", | |
| hdrs = ["my_lib.h"], | |
| aspect_hints = [":my_lib_rust.hint"], | |
| ) | |
| rust_api_from_cpp( | |
| name = "my_lib_rust", | |
| cpp_target = ":my_lib", | |
| ) | |
| ``` | |
| ### FAQ: How do separate rules work without cycles? {#faq_separate_rules} | |
| A library `A`, and its bindings `bindings(A)`, must be linked together in the | |
| build graph: if `B` uses a type from `A`, then `bindings(B)` uses a type from | |
| `bindings(A)`. | |
| If we were to point `my_lib` directly to `my_lib_rust` via `aspect_hints`, and | |
| `my_lib_rust` depended on `my_lib` (to trigger bindings generation), we would | |
| create a dependency cycle: `my_lib_rust` -> `my_lib` -> `my_lib_rust`. | |
| To avoid this, the `rust_api_from_cpp` macro splits the definition into two | |
| parts: | |
| 1. A configuration target (`my_lib_rust.hint`) which contains the configuration | |
| but does *not* depend on `my_lib`. | |
| 2. A bindings target (`my_lib_rust`) which depends on `my_lib`. | |
| The `cc_library` target (`my_lib`) then references the configuration target | |
| (`my_lib_rust.hint`) in its `aspect_hints`. | |
| The dependency flow is: `my_lib_rust` -> `my_lib` -> `my_lib_rust.hint` | |
| Since `my_lib_rust.hint` does not depend on `my_lib_rust` or `my_lib`, the cycle | |
| is broken. | |
| ### FAQ: Why are there extra dependencies in `deps(target)`? {#faq_dependency_edges} | |
| Because the Rust bindings are created using an **aspect** on the C++ target, | |
| everything that the Rust bindings need to depend on will appear in a Bazel query | |
| / depserver query for `deps(target)`. | |
| For example, if you wanted to add some extra source file to the Rust bindings, | |
| you might specify them in `aspect_hints`. This file will show up in | |
| `deps(target)`. | |
| These Rust-only deps are not used at all in pure-C++ builds (the Bazel actions | |
| registered by them won't be executed), but they will show up in the dependency | |
| graph anyway, due to how Bazel query and depserver track dependencies. | |
| NOTE: In particular, if your project has tests that count/limit the transitive | |
| dependencies of a C++ binary, they will overcount the dependencies, and the | |
| overcounting will get worse as Rust support is rolled out through the C++ build | |
| graph. | |
| </section> | |
| ## Wrapping and type bridging vs direct use of types {#bridging} | |
| Crubit automatically generates layout-compatible Rust equivalents of C++ types. | |
| When the C++ type is [Rust-movable](classes_and_structs.md#rust_movable), the | |
| Crubit-generated Rust type is Rust-movable, these can be used by value, by | |
| pointer, in struct fields, arrays, and any other compound data type. A C++ | |
| pointer `const T*` can become a Rust `*const T`, and a C++ `T` field can become | |
| a Rust `T` field, and so on, with few restrictions. | |
| For example, the following C++ type: | |
| ```c++ | |
| struct Vec2d { | |
| float x; | |
| float y; | |
| }; | |
| ``` | |
| Becomes (roughly) the following Rust type: | |
| ```rust | |
| #[repr(C)] | |
| struct Vec2d { | |
| pub x: f32, | |
| pub y: f32, | |
| } | |
| ``` | |
| These have an identical layout, and so a C++ pointer or field containing a C++ | |
| `Vec2d` is exactly equivalent to a Rust pointer or field containing a Rust | |
| `Vec2d`. | |
| (See [Types](../types/) for more information about layout-compatibility.) | |
| Because of this, it is often not required to manually write any new types. The | |
| bindings generated by Crubit will produce a working type automatically. | |
| ### When to wrap a type | |
| There are, still, a handful of reasons to manually write "wrapper" types which | |
| encapsulate or replace the original C++ type (or its Crubit-generated Rust | |
| type). | |
| * If the type is **not** naturally Rust-movable, but it's important for the | |
| Rust type to be Rust-movable. It may be possible to make changes to the C++ | |
| code to make the type Rust-movable using some of the strategies described in | |
| [the cookbook](cookbook.md#rust_movable). This allows the greatest | |
| flexibility, as the type becomes usable in almost every context. But if that | |
| is not possible, writing a new "wrapper" type can keep Rust programmers | |
| productive. | |
| * Some Rust types have very special semantics, which are impossible to | |
| implement in the bindings for a C++ type. For example, Rust has special | |
| support for `Result` and `Option` in error handling via the `?` operator, | |
| which cannot yet be implemented by `Status` or `std::optional` using stable | |
| Rust features. These privileged Rust types can be used instead of the | |
| equivalent C++ types, as a wrapper type. | |
| * The type is simply not supported/supportable in Rust, and needs a wrapper as | |
| a workaround. (See also: crubit.rs/errors/unsupported_type.) | |
| In these cases, Crubit may bridge to a wrapper type as a workaround, while we | |
| hopefully fix the underlying issues that mean we cannot directly use the | |
| underlying type. This offers us a subset of the API we want, and allows | |
| continued progress. | |
| ### Why not to wrap a type | |
| Wrapper types work best when passed by value: if you return a `T` in C++, the | |
| corresponding Rust function can automatically convert it to and return a | |
| `WrappedT`. | |
| However, no conversion is possible for references or fields, which really are | |
| the original type, with its size and alignment and address in memory - to make | |
| this work transparently requires an ever-expanding network of wrapper types, one | |
| for every compound data type that might contain `T`: | |
| * `T` must become `WrappedT` | |
| * `const T&`, if it is supported at all, must become something like | |
| `TRef<'a>`, or a dynamically sized `&TView`. | |
| * `std::vector<T>`, if it is supported at all, must become something like | |
| `TVector`. | |
| * `struct MyStruct {T x;}` must become a wrapped `WrappedMyStruct`. | |
| * ... | |
| The problems introduced by wrapper types can easily outweigh the benefits that | |
| they bring. Crubit aims to reduce their necessity to zero over time. | |
| #### *Bad reasons to wrap a type* | |
| In most other circumstances where one might *want* to reach for wrapper types, | |
| alternatives exist: | |
| * If we want to use a wrapper type in order to give the type a nicer Rust API, | |
| then, as an alternative, one can customize the Rust API of the wrapped type | |
| using an aspect hint. You can define new methods and trait implementations | |
| to the side, without altering any C++ code. | |
| * If we want to use a wrapper type in order to change the type invariants – to | |
| make them stricter or looser – this is fine, as long as it doesn't *replace* | |
| the not-as-nice type. For example, if a C++ API returns `std::string` | |
| (bytes, "probably" UTF-8), the Rust equivalent should not return a Rust | |
| `String` (Unicode, definitely UTF-8). Changing type invariants in-place | |
| causes some APIs to become impossible to call, and causes the Rust and C++ | |
| ecosystems to diverge and become incompatible. The bindings should be high | |
| fidelity. Wrapper types of this form should be optional, and available | |
| equally to both C++ and Rust to avoid fragmenting the ecosystem. | |
| </section> | |
| ## Fidelity {#fidelity} | |
| Anything possible in C++ should be possible in Rust. See | |
| [<internal link>](http://goto.google.com/no-hitchhikers). | |
| The Rust API for a given C++ API should not try to make the interface "better" | |
| at more than a superficial level, because it can compromise the ability of other | |
| teams to write new Rust code, or port existing C++ code to Rust. | |
| **Good changes:** | |
| * Changing method names, especially to names that Rust callers might expect. | |
| For example, changing `Status::ok()` (C++) to `Status::is_ok()` (Rust) – | |
| Rust callers expect many of these boolean functions to be prefixed with | |
| `is_`. | |
| * Adding new APIs that Rust users expect. For example, trait implementations | |
| that allow the type to better interoperate with the Rust ecosystem, or | |
| functions which accept a `Path` or `&str` in *addition* to a raw C++ | |
| `string_view`. | |
| * Reifying C++ comments around lifetime or safety as actual lifetime | |
| annotations or `unsafe` declarations. | |
| If the Rust type is outright unnatural to use, people won't use it, and it's | |
| worse for the ecosystem to have two APIs than one API. | |
| **Bad changes:** | |
| * Removing deprecated APIs which still have C++ callers. | |
| * Placing new requirements on Rust callers that were not placed on C++ | |
| callers, such as requiring UTF-8 when C++ does not. |