blob: e6fc9a36a84fcb2c14d2ecb32f51491770ea8589 [file] [view]
# `rust_api_from_cpp`
## Introduction
`rust_api_from_cpp` (aka `rs_bindings_from_cc`) is a Crubit tool that takes
C++ headers as input and generates Rust APIs (a Rust crate) as output,
enabling Rust to call C++.
This document provides instructions for using `rust_api_from_cpp` in Chromium.
See [`//docs/rust/crubit/README.md`](./README.md) for general information about
Crubit, including Crubit availability in projects other than Chromium.
[TOC]
## Availability
`rust_api_from_cpp` support is currently considered experimental and unstable.
The `rust_api_from_cpp` GN template can only be used when the
`enable_rust_api_from_cpp` GN arg is `true`. It defaults to `false` (see
`//build/config/rust.gni`), but can be set to `true` in `args.gn` for local
experimentation.
TODO(https://crbug.com/40226863): Edit this section once we officially declare
and announce support for using `rust_api_from_cpp`.
## Generating Rust bindings for a C++ target
Use the `rust_api_from_cpp` template to generate Rust bindings for the public
headers of a C++ target.
Example:
```cpp
// build/rust/tests/test_rust_api_from_cpp/self_contained_target_header2.h:
namespace test_ns {
int AddViaCc(int x, int y);
struct CcPodStruct final {
int value;
};
} // namespace test_ns
```
```gn
# build/rust/tests/test_rust_api_from_cpp/BUILD.gn
import("//build/rust/rust_api_from_cpp.gni")
import("//build/rust/rust_static_library.gni")
source_set("self_contained_target") {
sources = [
"self_contained_target_header2.cc",
"self_contained_target_header2.h",
]
}
rust_api_from_cpp("self_contained_target_rs_api") {
# This is the C++ target that the bindings are for / call into.
bindings_target = ":self_contained_target"
# Public headers from `sources` of `self_contained_target`.
public_headers = [
"self_contained_target_header2.h",
]
# Re-exports items of the `test_ns` C++ namespace at the root of the crate.
root_namespaces = [ "test_ns" ]
}
rust_static_library("rust_api_from_cpp_unittests") {
crate_root = "tests.rs"
sources = [ crate_root ]
deps = [ ":self_contained_target_rs_api" ]
}
```
```rust
// build/rust/tests/test_rust_api_from_cpp/tests.rs:
chromium::import! {
"//build/rust/tests/test_rust_api_from_cpp:self_contained_target_rs_api";
}
fn foo() {
let sum = ::self_contained_target_rs_api::AddViaCc(100, 42);
let x = ::self_contained_target_rs_api::CcPodStruct { value: 123 };
}
```
Notes:
* By convention, a `rust_api_from_cpp` target is named after its
`bindings_target` with an `_rs_api` suffix (e.g. `foo_rs_api` for `foo`).
* `public_headers` lists only the headers that provide the public API of the
`bindings_target`. Rust bindings are generated only for these headers.
* `root_namespaces` is optional. Without it, Rust code has to write
`self_contained_target_rs_api::test_ns::AddViaCc` (see the "Shorter paths for
items from C++ namespaces" section below).
## Inspecting the generated bindings
Let's assume that `rust_api_from_cpp` bindings are generated by
`//some/dir:foo_rs_api`. The generated bindings can then be found and
inspected in `<out_dir>/gen/some/dir/foo_rs_api.rs`. For example:
```sh
$ head -3 out/rel/gen/build/rust/tests/test_rust_api_from_cpp/self_contained_target_rs_api.rs
// Automatically @generated Rust bindings for the following C++ target:
// //build/rust/tests/test_rust_api_from_cpp:self_contained_target_rs_api
// Features: supported, types
```
## Specifying binding dependencies
If public APIs from `public_headers` use types from another C++ target, then
the bindings for that other target need to be listed in the `bindings_deps`
attribute of `rust_api_from_cpp`.
Example (`CreateCcPodStructFromValue` below returns `CcPodStruct` from the
`self_contained_target` used in the earlier examples):
```cpp
// build/rust/tests/test_rust_api_from_cpp/target_depending_on_another.h:
#include "build/rust/tests/test_rust_api_from_cpp/self_contained_target_header2.h"
namespace test_ns {
inline CcPodStruct CreateCcPodStructFromValue(int x) {
return CcPodStruct{.value = x};
}
} // namespace test_ns
```
```gn
# build/rust/tests/test_rust_api_from_cpp/BUILD.gn
source_set("target_depending_on_another") {
sources = [ "target_depending_on_another.h" ]
public_deps = [ ":self_contained_target" ]
}
rust_api_from_cpp("target_depending_on_another_rs_api") {
bindings_target = ":target_depending_on_another"
public_headers = [ "target_depending_on_another.h" ]
# Parallels `public_deps` of `target_depending_on_another`.
bindings_deps = [ ":self_contained_target_rs_api" ]
root_namespaces = [ "test_ns" ]
}
rust_static_library("rust_api_from_cpp_unittests") {
crate_root = "tests.rs"
sources = [ crate_root ]
deps = [
":self_contained_target_rs_api",
":target_depending_on_another_rs_api",
]
}
```
```rust
// build/rust/tests/test_rust_api_from_cpp/tests.rs:
chromium::import! {
"//build/rust/tests/test_rust_api_from_cpp:target_depending_on_another_rs_api";
}
fn foo() {
// `x` is a `::self_contained_target_rs_api::CcPodStruct`.
let x = ::target_depending_on_another_rs_api::CreateCcPodStructFromValue(456);
assert_eq!(x.value, 456);
}
```
Notes:
* `bindings_deps` of `rust_api_from_cpp` list other `rust_api_from_cpp` targets
(e.g. `self_contained_target_rs_api` above) - not the C++ targets (e.g. not
`self_contained_target`).
* `bindings_deps` only need to cover the C++ targets whose types appear in the
public APIs from `public_headers`. Other `deps` of the `bindings_target`
(e.g. ones used only by its `.cc` files) do not need Rust bindings.
### Types from Rust crates
If public APIs from `public_headers` use Rust types (from a `.h` file generated
by [`cpp_api_from_rust`](cpp_api_from_rust.md)), then `bindings_deps` should
list the `cpp_api_from_rust` target. The Rust bindings will then refer to the
original Rust types. See `cpp_lib_using_rust_type_rs_api` in
`build/rust/tests/test_rust_api_from_cpp/round_trip/BUILD.gn` for an example.
## References and lifetimes
Crubit binds C++ references (including `this`) as Rust references (or as
`CRef` / `CMut`). By default, the lifetimes of these references come from
Rust's lifetime elision rules. These rules can be wrong - for example, when a
returned reference can point into a parameter other than `this`. In such
cases, annotate the C++ API:
* Prefer `LIFETIME_BOUND` (from `base/compiler_specific.h`). Crubit translates
it into Rust lifetimes. Clang warnings also lightly check it (e.g.
`-Wdangling` reports a temporary passed to a `LIFETIME_BOUND` parameter
if the result outlives the temporary). See
`build/rust/tests/test_rust_api_from_cpp/lifetimes/lifetime_bound.h`.
* Use explicit lifetime annotations (e.g. `$a` from
`#include "third_party/crubit/support/lifetime_annotations.h"`) only if
`LIFETIME_BOUND` can't express the lifetimes. Nothing checks that these
annotations are correct. C++ targets that use these annotations need to
depend on `//build/rust/crubit`. See
`build/rust/tests/test_rust_api_from_cpp/lifetimes/explicit_lifetimes.h`.
See
[Crubit's "References and lifetimes" docs](https://crubit.rs/cpp/references_and_lifetimes.html)
for more details and examples.
See also https://crbug.com/572054453 and https://crbug.com/572056816 which track
enabling additional lifetime-related warnings in Chromium's C++ targets.
## C++ standard library types
Some C++ standard library types (e.g. `std::optional<T>`, `std::unique_ptr<T>`,
`std::vector<T>`, and `std::string_view`) are represented in the generated
bindings by layout-compatible Rust types from the `cpp_std` crate. To name
these types, Rust code should depend on `//build/rust/crubit:cpp_std` and write
e.g. `cpp_std::vector<T>`.
See `build/rust/tests/test_rust_api_from_cpp/std/std_types.h` and
`build/rust/tests/test_rust_api_from_cpp/std/unittests.rs`.
`std::string_view` is represented as `cpp_std::string_view<'a>` (see the
"References and lifetimes" section above).
## Types that Rust can't move
Rust moves values with `memcpy` - it cannot move C++ objects that are not Rust
movable. For more details please see the following sections of the Crubit book:
* ["Rust-movable classes"](https://crubit.rs/cpp/classes_and_structs.html#rust_movable)
(generic introduction)
* ["Making types Rust-movable"](https://crubit.rs/cpp/cookbook#rust_movable)
(tips for making a C++ object Rust-movable). Some extra notes about
`TRIVIAL_ABI`:
- Note that in Chromium `TRIVIAL_ABI` from `base/compiler_specific.h`
should be used instead of the equivalent Abseil macro.
- Note that Crubit docs and
[Clang docs](https://clang.llvm.org/docs/AttributeReference.html#trivial-abi:~:text=objects%20that%20contain%20pointers%20to%20themselves%20or%20otherwise%20depend%20on%20their%20address%20(or%20the%20address%20or%20their%20subobjects)
point out that misusing `TRIVIAL_ABI` can lead to
Undefined Behavior (UB) - e.g. when the attribute is applied to
objects that are not
[bitwise-relocatable (as defined by Crubit)](https://github.com/google/crubit/blob/4fd627869b9e9becbf4c041f7a511102c2db4ce1/docs/design/bitwise_relocatable.md?plain=1#L35-L52).
* `ctor` crate documentation (support for in-place construction and mutation of
Rust types like ones that were originally defined in C++):
- ["Quick guide"](https://crubit.rs/types/non_rust_movable/intro_short.html)
- ["Advanced guide"](https://crubit.rs/types/non_rust_movable/intro_advanced.html)
- ["Cheat sheet"](https://crubit.rs/types/non_rust_movable/cheat_sheet.html)
## Adding hand-written Rust code to the bindings
The `extra_rust_sources` attribute of `rust_api_from_cpp` merges hand-written
`.rs` files into the generated bindings crate. This is mostly useful for
implementing traits (e.g. `TryFrom`, `Display`, `Iterator`) for the types from
the generated bindings. Rust's
[orphan rule](https://doc.rust-lang.org/reference/items/implementations.html#r-items.impl.trait.orphan-rule)
prevents such trait impls in other crates (see also the "Troubleshooting"
section below).
Example:
```cpp
// build/rust/tests/test_rust_api_from_cpp/extra_rust_sources/time_delta.h:
namespace test_base {
class TestTimeDelta {
public:
static constexpr TestTimeDelta FromMicroseconds(int64_t us);
constexpr int64_t InMicroseconds() const;
...
};
} // namespace test_base
```
```rust
// build/rust/tests/test_rust_api_from_cpp/extra_rust_sources/time_delta.rs:
use crate::test_base::TestTimeDelta;
use core::num::TryFromIntError;
use core::time::Duration;
impl TryFrom<TestTimeDelta> for Duration {
type Error = TryFromIntError;
fn try_from(delta: TestTimeDelta) -> Result<Self, Self::Error> {
let us = u64::try_from(delta.InMicroseconds())?;
Ok(Duration::from_micros(us))
}
}
```
```gn
# build/rust/tests/test_rust_api_from_cpp/extra_rust_sources/BUILD.gn
rust_api_from_cpp("time_delta_rs_api") {
bindings_target = ":time_delta"
public_headers = [ "time_delta.h" ]
root_namespaces = [ "test_base" ]
extra_rust_sources = [ "time_delta.rs" ]
}
```
```rust
// build/rust/tests/test_rust_api_from_cpp/extra_rust_sources/unittests.rs:
chromium::import! {
"//build/rust/tests/test_rust_api_from_cpp/extra_rust_sources:time_delta_rs_api"
as time_delta;
}
fn foo() {
let delta = ::time_delta::TestTimeDelta::FromMicroseconds(123);
let duration = std::time::Duration::try_from(delta).unwrap();
}
```
Notes:
* Each file from `extra_rust_sources` becomes a private module at the root of
the crate. All items of this module are re-exported (via `pub use`) from the
root of the crate.
* Prefer to keep `extra_rust_sources` limited to Rust-specific glue (e.g. trait
impls or conversions to Rust standard library types). If an API would also be
useful to C++ callers, then add it to the C++ header instead - Crubit will
generate Rust bindings for it, and both languages will share one
implementation.
* Injecting Rust sources into a _nested module_ of the generated bindings is
supported via the `extra_rust_source_groups` attribute. Each group lists
`sources` and a Rust module path in `mod` (e.g. `"foo::bar"`) - the sources
are placed into that module. Reaching for this attribute should be
needed quite rarely - usually only for `cc_std` or `base` bindings. See
`source_groups_rs_api` in
[`build/rust/tests/test_rust_api_from_cpp/extra_rust_sources/BUILD.gn`](../../../build/rust/tests/test_rust_api_from_cpp/extra_rust_sources/BUILD.gn)
for an example.
## Shorter paths for items from C++ namespaces
C++ namespaces become Rust modules in the generated bindings (e.g.
`test_outer::test_inner::GetValueFromNestedNamespace` from C++ becomes
`test_root_namespaces::test_outer::test_inner::GetValueFromNestedNamespace` in
Rust). The `root_namespaces` attribute of `rust_api_from_cpp` also re-exports
the items of the specified namespaces at the root of the crate.
Example:
```gn
# base/BUILD.gn
rust_api_from_cpp("base_rs_api") {
bindings_target = ":base"
root_namespaces = [ "base" ]
# ...
}
```
## Troubleshooting
The sections below should help diagnose and fix some issues you may encounter.
If your issue is not covered, then please see
[`docs/rust/crubit/README.md`](./README.md) for instructions
how to report a new bug.
### APIs missing from the generated bindings
If `rust_api_from_cpp` is unable to generate bindings for a given C++ API,
then the generated `.rs` file will contain a comment explaining why.
#### Crubit is not enabled on defining target
If you see an error like:
```
$ cat out/rel/gen/build/rust/tests/test_rust_api_from_cpp/target_depending_on_another_rs_api.rs
...
// error: function `CreateCcPodStructFromValue` could not be bound
// Cannot use an error type `CcPodStruct` by value:
// Crubit is not enabled on defining target:
// ../../build/rust/tests/test_rust_api_from_cpp/self_contained_target_header2.h
...
```
Then you want to read the "Specifying binding dependencies" section above.
### Can't implement a trait for a type from the generated bindings
Rust's
[orphan rule](https://doc.rust-lang.org/reference/items/implementations.html#r-items.impl.trait.orphan-rule)
prevents implementing a foreign trait for foreign types. For example, if
`extra_rust_sources/unittests.rs` tries to implement `TryFrom` for a type from
`rust_api_from_cpp` bindings, then the build fails with an error like:
```
error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
--> ../../build/rust/tests/test_rust_api_from_cpp/extra_rust_sources/unittests.rs:47:1
|
47 | impl TryFrom<::time_delta::test_base::TestTimeDelta> for std::time::Duration {
| ^^^^^-----------------------------------------------^^^^^-------------------
| | |
| | `Duration` is not defined in the current crate
| this is not defined in the current crate because this is a foreign trait
```
To fix this, move the trait impl into the generated bindings crate by using
`extra_rust_sources` (see the "Adding hand-written Rust code to the bindings"
section above).
### Dependency cycles and missing dependencies
`rust_api_from_cpp` parses `public_headers` (and all headers that they
`#include`, directly or transitively) when it generates the bindings. Therefore
GN dependencies need to be declared correctly and completely: all these headers
need to be (transitively) provided by `bindings_target`.
This is stricter than what Chromium needed in the past:
* Many Chromium targets are big and monolithic (e.g. `//chrome/browser` or
`//content/browser`).
* GN does not enforce correct dependencies as strictly as some other build
systems:
* `gn check` ignores generated files by default (see `--check-generated` in
the [GN reference for `gn check`](https://gn.googlesource.com/gn/+/main/docs/reference.md#cmd_check)).
https://crbug.com/558554239 tracks fixing the violations in generated
files (see `no_check_generated_targets` in [`//.gn`](../../../.gn)).
* By default `gn check` allows public headers to `#include` headers from
private `deps`. Targets can opt into stricter checks with
`check_includes_strict = true` (see
[`build_performance.md`](./build_performance.md)).
[`cxx`](https://cxx.rs) does not read C++ headers when it generates its
bindings. The headers are used only later, when the generated `.cc` file is
compiled. Therefore `cxx` bindings may compile even if their dependencies are
missing (e.g. because a generated header was already generated for another
target). This can cause flaky builds. For example,
`no_check_generated_targets` (see above) has several `..._cxx_generated`
entries, because `gn check --check-generated` reports missing dependencies in
the C++ files generated by `cxx`. When you replace `cxx` with
`rust_api_from_cpp`, you may have to add the missing dependencies.
This is mostly a problem when Rust is added in the middle of a big target. For
example, if C++ code in `//content/browser` calls Rust, and this Rust code calls
other C++ APIs from `//content/browser`, then GN reports a dependency cycle.
To fix this, move the C++ APIs that Rust calls into a separate, smaller target
(see also the "Modularity" section in
[`chrome_browser_design_principles.md`](../../chrome_browser_design_principles.md#modularity)).
There is no such problem when Rust only calls C++ APIs from lower layers (e.g.
from `//base`), unless the bindings of these APIs are also used by Rust code
inside the lower layer.
Smaller targets with correct dependencies are good for Chromium even without
Rust. They make API boundaries clearer, and they can make builds faster and
more reliable.
### Different bindings on different target platforms
Public API of Crubit-generated bindings should be the same on all target
platforms. There are some known issues where the bindings may differ (e.g. see
Google-internal b/564616479), but please do not work around the differences with
something like `#[cfg(windows)]` - please report bugs instead.