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 for general information about Crubit, including Crubit availability in projects other than Chromium.

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:

// 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:

  • 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:

$ 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):

// 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.

Types from Rust crates

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.

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 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:

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 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:

  • 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 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:

# 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 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 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://crbug.com/558554239 tracks fixing the violations in generated files (see no_check_generated_targets in //.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).

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.

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.