rust_api_from_cpprust_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 for general information about Crubit, including Crubit availability in projects other than Chromium.
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.
Use the rust_api_from_cpp template to generate Rust bindings for the public headers of a C++ target.
Example:
// 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
# 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" ] }
// 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:
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).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:
$ 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
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):
// 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
# 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", ] }
// 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.If public APIs from public_headers use Rust types (from a .h file generated by cpp_api_from_rust), 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.
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:
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.$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 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.
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).
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:
TRIVIAL_ABI:TRIVIAL_ABI from base/compiler_specific.h should be used instead of the equivalent Abseil macro.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).ctor crate documentation (support for in-place construction and mutation of Rust types like ones that were originally defined in C++):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 prevents such trait impls in other crates (see also the “Troubleshooting” section below).
Example:
// 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
// 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)) } }
# 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" ] }
// 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:
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.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.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 for an example.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:
# base/BUILD.gn rust_api_from_cpp("base_rs_api") { bindings_target = ":base" root_namespaces = [ "base" ] # ... }
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 for instructions how to report a new bug.
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.
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.
Rust's 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).
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:
//chrome/browser or //content/browser).gn check ignores generated files by default (see --check-generated in the GN reference for gn check). https://crbug.com/558554239 tracks fixing the violations in generated files (see no_check_generated_targets in //.gn).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).cxx 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). 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.
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.