Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust bindings for C++ libraries

When a C++ library enables Crubit, that library can be used directly from Rust. 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/cpp/function/. The other examples in https://github.com/google/crubit/tree/main/examples/cpp/ are also useful. If you prefer just copy-pasting something, start there.

How to use Crubit

Crubit allows you to call some C++ interfaces from Rust. It supports functions, classes and structs, and enums. Crubit does not support advanced features like templates or virtual inheritance.

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

  1. Define a rust_api_from_cpp target in the same BUILD file as your cc_library.

  2. Add the generated .hint target (e.g. :<name_of_rust_target>.hint) to the aspect_hints of your cc_library.

  3. Depend on the rust_api_from_cpp target in the deps of your Rust target.

The bindings can be previewed using the following command:

```sh
$ bazel build --config=crubit-genfiles //path/to:target
```

Write a cc_library target

The first part of creating a library that can be used by Crubit is to write a cc_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

#ifndef THIRD_PARTY_CRUBIT_EXAMPLES_CPP_FUNCTION_EXAMPLE_H_
#define THIRD_PARTY_CRUBIT_EXAMPLES_CPP_FUNCTION_EXAMPLE_H_

#include <stdint.h>

namespace gshoe {

inline int32_t add_two_integers(int32_t x, int32_t y) { return x + y; }

}  // namespace gshoe

#endif  // THIRD_PARTY_CRUBIT_EXAMPLES_CPP_FUNCTION_EXAMPLE_H_

If you write a BUILD target as normal, it will not actually get Crubit bindings, but we’ll start from there:

load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("//rs_bindings_from_cc/bazel_support:rust_api_from_cpp.bzl", "rust_api_from_cpp")
load(
    "//rs_bindings_from_cc/test/golden:golden_test.bzl",
    "golden_test",
)

licenses(["notice"])

cc_library(
    name = "example_lib",
    hdrs = ["example.h"],

    # Opt into using Crubit.
    aspect_hints = [":example_lib_rust.hint"],
)

rust_api_from_cpp(
    name = "example_lib_rust",
    cpp_target = ":example_lib",
)

rust_binary(
    name = "main",
    srcs = ["main.rs"],

    # Declare a dependency on Rust bindings for calling into the C++ `example_lib` library:
    deps = [":example_lib_rust"],
)

# This test rule is just to make sure we don't forget to keep the `example_generated.rs` updated!
golden_test(
    name = "example_golden_test",
    cc_library = "example_lib",
    golden_rs = "example_generated.rs",
)

# This BUILD rule is here for exposition only. This is an example of what happens if
# you _don't_ set things up for Crubit.
cc_library(
    name = "example_lib_broken",
    hdrs = ["example.h"],
)

Enable Crubit on a target

To enable Crubit on a C++ target, you must define a rust_api_from_cpp target associated with it, and link them using aspect_hints.

Behind the scenes, Rust APIs are generated via Bazel aspects which run on the cc_library target. When examining a cc_library, Rust API generation looks for the aspect_hints so that it can find the corresponding rust_api_from_cpp target.

Define a rust_api_from_cpp target in the same BUILD file as your cc_library, and add its .hint target to the aspect_hints of the cc_library:

load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("//rs_bindings_from_cc/bazel_support:rust_api_from_cpp.bzl", "rust_api_from_cpp")
load(
    "//rs_bindings_from_cc/test/golden:golden_test.bzl",
    "golden_test",
)

licenses(["notice"])

cc_library(
    name = "example_lib",
    hdrs = ["example.h"],

    # Opt into using Crubit.
    aspect_hints = [":example_lib_rust.hint"],
)

rust_api_from_cpp(
    name = "example_lib_rust",
    cpp_target = ":example_lib",
)

rust_binary(
    name = "main",
    srcs = ["main.rs"],

    # Declare a dependency on Rust bindings for calling into the C++ `example_lib` library:
    deps = [":example_lib_rust"],
)

# This test rule is just to make sure we don't forget to keep the `example_generated.rs` updated!
golden_test(
    name = "example_golden_test",
    cc_library = "example_lib",
    golden_rs = "example_generated.rs",
)

# This BUILD rule is here for exposition only. This is an example of what happens if
# you _don't_ set things up for Crubit.
cc_library(
    name = "example_lib_broken",
    hdrs = ["example.h"],
)
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("//rs_bindings_from_cc/bazel_support:rust_api_from_cpp.bzl", "rust_api_from_cpp")
load(
    "//rs_bindings_from_cc/test/golden:golden_test.bzl",
    "golden_test",
)

licenses(["notice"])

cc_library(
    name = "example_lib",
    hdrs = ["example.h"],

    # Opt into using Crubit.
    aspect_hints = [":example_lib_rust.hint"],
)

rust_api_from_cpp(
    name = "example_lib_rust",
    cpp_target = ":example_lib",
)

rust_binary(
    name = "main",
    srcs = ["main.rs"],

    # Declare a dependency on Rust bindings for calling into the C++ `example_lib` library:
    deps = [":example_lib_rust"],
)

# This test rule is just to make sure we don't forget to keep the `example_generated.rs` updated!
golden_test(
    name = "example_golden_test",
    cc_library = "example_lib",
    golden_rs = "example_generated.rs",
)

# This BUILD rule is here for exposition only. This is an example of what happens if
# you _don't_ set things up for Crubit.
cc_library(
    name = "example_lib_broken",
    hdrs = ["example.h"],
)

The .hint target is automatically created by the rust_api_from_cpp macro (named <name>.hint) and is used to avoid circular dependencies between the C++ library and the generated Rust API.

Note that having Rust callers does constrain library evolution. Certain changes cannot be made in C++ without breaking Rust callers, unless care is taken. crubit.rs/cpp/cookbook#compatibility

Look at the generated bindings

To examine the generated C++ bindings for the target, you can run the following command:

$ bazel build --config=crubit-genfiles //examples/cpp/function:example_lib_broken

This is the best way to preview the generated bindings for a given C++ target right now. You might end up using this a lot, so keep it in your shell history.

If you run the above command, you should see some output like the following:

Aspect //rs_bindings_from_cc/bazel_support:rust_bindings_from_cc_aspect.bzl%rust_bindings_from_cc_aspect of //examples/cpp/function:example_lib up-to-date:
  bazel-bin/examples/cpp/function/example_lib_rust_api_impl.cc
  bazel-bin/examples/cpp/function/example_lib_rust_api.rs
  bazel-bin/examples/cpp/function/example_lib_namespaces.json

These files are the generated bindings which are used under the hood when depending on a C++ target from Rust. They consist of:

  1. The supporting C++ code to glue Rust and C++ together. (The .cc file.)
  2. The public Rust interface. (The .rs file.)
  3. Supporting internal implementation details. (The .json file.)

You don’t need to check them in, as they are regenerated automatically whenever you build a Rust build target which depends on C++.

The .rs file is the interesting one for end users. It should contain an actually useful API for the target:

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

// Automatically @generated Rust bindings for the following C++ target:
// //examples/cpp/function:example_lib

#![rustfmt::skip]
#![feature(custom_inner_attributes)]
#![allow(stable_features)]
#![allow(improper_ctypes)]
#![allow(nonstandard_style)]
#![allow(unused)]
#![allow(deprecated)]
#![allow(unknown_lints, suspicious_runtime_symbol_definitions)]
#![deny(warnings)]
pub mod gshoe {
    #[inline(always)]
    pub fn add_two_integers(x: i32, y: i32) -> i32 {
        unsafe { crate::detail::__rust_thunk___ZN5gshoe16add_two_integersEii(x, y) }
    }
}

// namespace gshoe

mod detail {
    #[allow(unused_imports)]
    use super::*;
    unsafe extern "C" {
        pub(crate) unsafe fn __rust_thunk___ZN5gshoe16add_two_integersEii(x: i32, y: i32) -> i32;
    }
}

Use a C++ library from Rust

To depend on a C++ library from Rust, add the corresponding rust_api_from_cpp target to your Rust target’s deps:

load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("//rs_bindings_from_cc/bazel_support:rust_api_from_cpp.bzl", "rust_api_from_cpp")
load(
    "//rs_bindings_from_cc/test/golden:golden_test.bzl",
    "golden_test",
)

licenses(["notice"])

cc_library(
    name = "example_lib",
    hdrs = ["example.h"],

    # Opt into using Crubit.
    aspect_hints = [":example_lib_rust.hint"],
)

rust_api_from_cpp(
    name = "example_lib_rust",
    cpp_target = ":example_lib",
)

rust_binary(
    name = "main",
    srcs = ["main.rs"],

    # Declare a dependency on Rust bindings for calling into the C++ `example_lib` library:
    deps = [":example_lib_rust"],
)

# This test rule is just to make sure we don't forget to keep the `example_generated.rs` updated!
golden_test(
    name = "example_golden_test",
    cc_library = "example_lib",
    golden_rs = "example_generated.rs",
)

# This BUILD rule is here for exposition only. This is an example of what happens if
# you _don't_ set things up for Crubit.
cc_library(
    name = "example_lib_broken",
    hdrs = ["example.h"],
)

At that point, the bindings are directly usable from Rust. The interface is identical to the .rs file previewed earlier, but can be used directly:

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

fn main() {
    let sum = example_lib::gshoe::add_two_integers(2, 2);
    println!("sum = {sum}");
}

Common Errors

See crubit.rs/errors

Unsupported features

Some features are either unsupported, or else only supported with experimental feature flags . In order to get bindings for a C++ interface, that interface must only use the subset of features currently supported.

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:

  • Hide unsupported types behind a wrapper. For example, a std::set<T> is not supported, but a struct which wraps a set::set<T> is. crubit.rs/errors/unsupported_type describes the process in more detail.
  • Wrap unsupported functions, in general, behind wrappers.