C++ bindings for Rust libraries

Rust libraries can be used directly from C++. This page documents roughly what that entails, and additional subpages (available in the left-hand navigation) document specific aspects of the generated bindings.

Tip: The code examples below are pulled straight from

https://github.com/google/crubit/tree/main/examples/rust/function/. The other examples in https://github.com/google/crubit/tree/main/examples/rust/ are also useful. If you prefer just copy-pasting something, start there.

How to use Crubit

Crubit allows you to call some Rust interfaces from C++. It supports functions (including methods), structs, and even enums as "opaque" objects. Crubit does not support advanced features like generics or dynamic dispatch with dyn.

The rest of this document goes over how to create a Rust library that can be called from C++, and how to actually use it from C++. The quick summary is:

  • All rust_library targets can receive C++ bindings.

  • To use the bindings for a target //path/to:example_crate, you must create a C++ rule exporting the bindings, using cc_bindings_from_rust(name="any_name_here", crate=":example_crate").

  • The header name is the Rust target's label with a .h appended: to include the header for the Rust library //path/to:example_crate, you use #include "path/to/example_crate.h".

  • The namespace name is the Rust target name, e.g. example_crate. To change the namespace, use cc_bindings_from_rust_library_config, described below.

  • To see the generated C++ API, right click the "path/to/example_crate.h" include in Cider, and select "Go to Definition".

    NOTE: In some cases the generated file in Cider may be out of date. If it isn't refreshing, you can manually inspect the bindings using the workaround command in b/391395849.

Write a rust_library target

The first part of creating a library that can be used by Crubit is to write a rust_library target. For example:

// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

pub fn add_two_integers(x: i32, y: i32) -> i32 {
    x + y
}

In the BUILD file, in addition to defining the rust_library, you should also define the cc_bindings_from_rust target to make it easier to use from C++:

load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load(
    "@rules_rust//rust:defs.bzl",
    "rust_library",
)
load(
    "//cc_bindings_from_rs/bazel_support:cc_bindings_from_rust_rule.bzl",
    "cc_bindings_from_rust",
)
load(
    "//cc_bindings_from_rs/test/golden:golden_test.bzl",
    "golden_test",
)

package(default_applicable_licenses = ["//:license"])

licenses(["notice"])

# This declares an "example_crate_cc_api" target that provides Crubit-generated
# C++ bindings for the Rust crate behind the `":example_crate"` target.
rust_library(
    name = "example_crate",
    srcs = ["example.rs"],
)

cc_bindings_from_rust(
    name = "example_crate_cc_api",
    crate = ":example_crate",
)

cc_binary(
    name = "main",
    srcs = ["main.cc"],
    deps = [":example_crate_cc_api"],
)

golden_test(
    name = "example_golden_test",
    golden_h = "example_generated.h",
    rust_library = "example_crate",
)

Example: If your Rust library is named //path/to:example_crate, then the C++ header file is "path/to/example_crate.h", and the C++ namespace is example_crate by default.

Use a Rust library from C++

C++ build rules do not have a rust_deps parameter, so to depend on the C++ bindings for a target, they must depend on the cc_bindings_from_rust rule.

For example:

load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load(
    "@rules_rust//rust:defs.bzl",
    "rust_library",
)
load(
    "//cc_bindings_from_rs/bazel_support:cc_bindings_from_rust_rule.bzl",
    "cc_bindings_from_rust",
)
load(
    "//cc_bindings_from_rs/test/golden:golden_test.bzl",
    "golden_test",
)

package(default_applicable_licenses = ["//:license"])

licenses(["notice"])

# This declares an "example_crate_cc_api" target that provides Crubit-generated
# C++ bindings for the Rust crate behind the `":example_crate"` target.
rust_library(
    name = "example_crate",
    srcs = ["example.rs"],
)

cc_bindings_from_rust(
    name = "example_crate_cc_api",
    crate = ":example_crate",
)

cc_binary(
    name = "main",
    srcs = ["main.cc"],
    deps = [":example_crate_cc_api"],
)

golden_test(
    name = "example_golden_test",
    golden_h = "example_generated.h",
    rust_library = "example_crate",
)
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <cstdint>
#include <iostream>

// The generated bindings are in a header at the same path as the
// `example_crate` rust_library, with a `.h` suffix.
#include "examples/rust/function/example_crate.h"

int main(int argc, char* argv[]) {
  // The generated bindings are in a namespace with the same name as the
  // target crate:
  int32_t sum = example_crate::add_two_integers(2, 2);
  std::cout << "sum = " << sum << std::endl;
  return 0;
}

NOTE: Other than for declaring the dependency, all other information about the generated bindings comes from the actual rust_library rule. For example, the #include for the above is #include "examples/rust/function/example_crate.h", not example_crate_cc_api.h.

(Optional) Customize the generated C++ API

Give it a better namespace

The crate name might make a poor namespace. In addition, typically, multiple C++ headers and build targets share the same namespace. To customize the namespace name, use cc_bindings_from_rust_library_config:

load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load(
    "@rules_rust//rust:defs.bzl",
    "rust_library",
)
load("//cc_bindings_from_rs/bazel_support:cc_bindings_from_rust_library_config_aspect_hint.bzl", "cc_bindings_from_rust_library_config")
load(
    "//cc_bindings_from_rs/bazel_support:cc_bindings_from_rust_rule.bzl",
    "cc_bindings_from_rust",
)
load(
    "//cc_bindings_from_rs/test/golden:golden_test.bzl",
    "golden_test",
)

package(default_applicable_licenses = ["//:license"])

licenses(["notice"])

# This configuration places all names inside the namespace `my::library`, instead of the crate name.
cc_bindings_from_rust_library_config(
    name = "custom_namespace",
    namespace = "my::library",
)

# This declares an "example_crate_cc_api" target that provides Crubit-generated
# C++ bindings for the Rust crate behind the `":example_crate"` target.
rust_library(
    name = "example_crate",
    srcs = ["example.rs"],
    aspect_hints = [":custom_namespace"],
)

cc_bindings_from_rust(
    name = "example_crate_cc_api",
    crate = ":example_crate",
)

cc_binary(
    name = "main",
    srcs = ["main.cc"],
    deps = [":example_crate_cc_api"],
)

golden_test(
    name = "example_golden_test",
    golden_h = "example_generated.h",
    rust_library = "example_crate",
)

Now, instead of the crate name, the generated bindings will use the namespace name you provided:

// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <cstdint>
#include <iostream>

#include "examples/rust/library_config/example_crate.h"

int main(int argc, char* argv[]) {
  // The generated bindings are in `my::library`, not `example_crate`.
  int32_t sum = my::library::add_two_integers(2, 2);
  std::cout << "sum = " << sum << std::endl;
  return 0;
}

Look at the generated bindings

There are two ways to look at the generated header file:

  • Click through the #include in Cider. Given the following C++ code:

    #include "path/to/example_crate.h"
    

    If you right click the file path, and select "Go to Definition", you will be taken to a file starting with // Automatically @generated C++ bindings.

  • Run bazel build //path/to:example_crate --config=crubit-genfiles, and open bazel-bin/path/to/example_crate.h in your text editor of choice.

Common Errors

Unsupported features

Some features are either unsupported, or else only supported with experimental feature flags (crubit.rs-features). In order to get bindings for a Rust interface, that interface must only use the subset of features currently supported.

For a particularly notable example, references are only supported as function parameters, and only in a subset of cases that we can prove does not add aliasing UB to C++ callers.

The way to work around this kind of problem, in all cases, is to wrap or hide the problematic interface behind an interface Crubit can handle:

  • Use raw pointers instead of references, if this use of references falls into a case Crubit does not support.
  • Hide unsupported types behind a wrapper type. For example, a Vec<T> is not supported by Crubit, but pub struct MyStruct(Vec<i32>); is.