Inline C++
Crubit allows Rust code to call C++ libraries directly through generated bindings. However, some C++ interfaces cannot yet be automatically bound by Crubit, such as function templates and preprocessor macros.
Crubit provides two macros, inline_cpp! and global_cpp!, that allow you to
embed C++ declarations and expressions directly inside your Rust source files.
This lets you call unsupported C++ code without having to write separate C++
wrapper libraries.
How to use Embedded C++
Embedded C++ uses two macros depending on scope:
global_cpp!: Used at module scope (outside functions) to declare C++#includeheaders, structs, namespaces, and helper functions.inline_cpp!: Used inside Rust functions to define and call an inline C++ expression.
You can use embedded C++ in two ways:
- In a
rust_api_from_cpptarget (Recommended): When extending the generated Rust bindings of an existing C++ library using custom Rust source files (additional_rust_srcs_for_crubit_bindings). - In a
rust_library_with_embedded_cpptarget: When authoring a standalone Rust library with embedded C++ without an underlyingcc_library.
1. Using embedded C++ in a rust_api_from_cpp target
If you maintain a cc_library and want to provide hand-written Rust helper
methods or trait implementations alongside your generated Crubit API, you can
pass custom Rust source files directly to rust_api_from_cpp via srcs.
Given a C++ header containing a template method that cannot be natively bound by Crubit:
// widget.h
namespace widget {
class Widget {
public:
template <typename T = void>
static std::unique_ptr<Widget> Create(absl::string_view name);
private:
Widget(absl::string_view name);
};
} // namespace widget
You can define the BUILD targets as follows:
load(
"//rs_bindings_from_cc/bazel_support:rust_api_from_cpp.bzl",
"rust_api_from_cpp",
)
cc_library(
name = "widget",
hdrs = ["widget.h"],
srcs = ["widget.cc"],
aspect_hints = [":widget_rust.hint"],
deps = [
"@abseil-cpp//absl/strings",
],
)
rust_api_from_cpp(
name = "widget_rust",
cpp_target = ":widget",
root_namespaces = ["widget"],
srcs = ["widget_custom.rs"],
)
Inside widget_custom.rs, you can use inline_cpp! to bridge the C++ template
factory method directly into the crate’s public API:
#![allow(unused)]
fn main() {
// widget_custom.rs (compiled directly into the widget_rust crate)
use crubit_support::inline_cpp;
impl Widget {
/// Ergonomic Rust wrapper over a C++ factory function template.
pub fn new_with_name(name: &str) -> cc_std::std::unique_ptr<Widget> {
let make_widget = inline_cpp! {
(rs_std::StrRef name) -> std::unique_ptr<widget::Widget> {
return widget::Widget::Create(name.to_string_view());
}
};
make_widget(name)
}
}
}
2. Using embedded C++ in a standalone rust_library target
To write a standalone Rust library that uses embedded C++ without an underlying
cc_library, define your target with rust_library_with_embedded_cpp:
# 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
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
load("//support/extract_cpp_from_rust:rust_library_with_embedded_cpp.bzl", "rust_library_with_embedded_cpp")
package(default_applicable_licenses = ["//:license"])
licenses(["notice"])
rust_library_with_embedded_cpp(
name = "headers_lib",
srcs = ["headers.rs"],
deps_of_cc_library = [
"@abseil-cpp//absl/strings",
"//support/rs_std:str_ref",
],
)
rust_library_with_embedded_cpp(
name = "math_lib",
srcs = ["math.rs"],
)
rust_library_with_embedded_cpp(
name = "greeting_lib",
srcs = ["greeting.rs"],
deps_of_cc_library = [
"@abseil-cpp//absl/strings",
"//support/rs_std:str_ref",
],
)
rust_library_with_embedded_cpp(
name = "point_lib",
srcs = ["point.rs"],
)
rust_library_with_embedded_cpp(
name = "clamp_lib",
srcs = ["clamp.rs"],
)
rust_library_with_embedded_cpp(
name = "math_helper_lib",
srcs = ["math_helper.rs"],
)
rust_binary(
name = "main",
srcs = ["main.rs"],
deps = [
":clamp_lib",
":greeting_lib",
":math_helper_lib",
":math_lib",
":point_lib",
],
)
rust_test(
name = "example_test",
srcs = ["example_test.rs"],
deps = [
":clamp_lib",
":greeting_lib",
":headers_lib",
":math_helper_lib",
":math_lib",
":point_lib",
"@crate_index//:googletest",
],
)
The deps_of_cc_library attribute lists the C++ libraries that provide the
headers and symbols used in your embedded C++ snippets. For a complete runnable
example, see
examples/cpp/inline_cpp/.
Writing Embedded C++
Declaring Headers with global_cpp!
Use global_cpp! at module scope (outside functions, including within nested
submodules) to specify #include headers and define supporting C++ types:
// 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
use crubit_support::global_cpp;
// Declaring C++ headers and types:
global_cpp! {
#include <algorithm>
#include <cmath>
#include "support/rs_std/str_ref.h"
#include "third_party/absl/strings/str_cat.h"
#include "third_party/absl/strings/string_view.h"
struct Point {
double x;
double y;
};
template <typename T>
struct MathHelper {
static T Multiply(T a, T b) { return a * b; }
};
}
NOTE: All
global_cpp!declarations across a crate (including those inside nested submodules) are combined into a single C++ companion header. Anyinline_cpp!block in the crate can access types declared inglobal_cpp!, regardless of which Rust module defined them. Standard Rust visibility (pub,pub(crate)) governs access to the wrapping Rust functions.
Calling C++ with inline_cpp!
Inside a Rust function, inline_cpp! defines an inline C++ expression. The
syntax requires a C++ parameter list, a return type arrow ->, and the C++
function body.
For example, calling a C++ standard library math function:
// 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
use crubit_support::{global_cpp, inline_cpp};
global_cpp! {
#include <cmath>
}
/// Computes the hypotenuse using C++ `std::hypot`.
pub fn compute_hypotenuse(a: f64, b: f64) -> f64 {
let hypot_fn = inline_cpp! {
(double a, double b) -> double {
return std::hypot(a, b);
}
};
hypot_fn(a, b)
}
Or calling an Abseil string utility:
// 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
use crubit_support::{global_cpp, inline_cpp};
global_cpp! {
#include "support/rs_std/str_ref.h"
#include "third_party/absl/strings/str_cat.h"
}
/// Formats a greeting using C++ `absl::StrCat`.
pub fn format_greeting(name: &str) -> String {
let greet = inline_cpp! {
(rs_std::StrRef name) -> std::string {
return absl::StrCat("Hello, ", name);
}
};
let cpp_str = greet(name);
cpp_str.to_string().expect("Valid UTF-8")
}
inline_cpp! produces a callable Rust closure. The parameter and return types
of the generated closure match the corresponding Rust types generated by Crubit
(for example, i32 for int, and &Point for const Point&).
Passing types between Rust and C++
Primitive types
Primitive numeric types and booleans
pass directly by value between Rust and C++ (for example, int in C++
corresponds to i32 in Rust, and double corresponds to f64).
References and pointers
You can pass Rust references to C++ as const T& or raw pointers:
// 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
use crubit_support::{global_cpp, inline_cpp};
pub use inline_cpp_generated_bindings::Point;
global_cpp! {
#include <cmath>
struct Point {
double x;
double y;
};
}
/// Calculates Euclidean distance for a `Point`.
pub fn get_distance(p: &Point) -> f64 {
let calc = inline_cpp! {
(const Point& p) -> double {
return std::sqrt(p.x * p.x + p.y * p.y);
}
};
calc(p)
}
WARNING: Standard Rust lifetime rules apply. C++ code must not store or keep references or pointers after the
inline_cpp!call returns.
Common Use Cases
Calling and Defining C++ Templates
Crubit does not automatically generate Rust bindings for uninstantiated C++ templates. With embedded C++, you can:
-
Instantiate library templates: Call templated functions or classes from included C++ headers inside
inline_cpp!:cs/file:examples/cpp/inline_cpp/clamp.rs function:clamp_value -
Define helper templates in
global_cpp!: Define C++ template helpers at module scope, then instantiate them with concrete types ininline_cpp!:cs/file:examples/cpp/inline_cpp/math_helper.rs function:multiply_ints
Common Errors
inline_cpp! uses Crubit under the hood, so it can produce any error Crubit
could produce when bridging types.
Missing parameter list or return type
inline_cpp! requires an explicit parameter list and return type arrow. If your
C++ snippet takes no arguments or returns nothing, specify () -> void:
#![allow(unused)]
fn main() {
let no_args_fn = inline_cpp! {
() -> void {
DoWork();
}
};
no_args_fn();
}
Omitting the parameter list or return type results in an extraction syntax error:
error: inline_cpp! block must start with a parameter list `(args)`
--> src/lib.rs:10:5
|
10 | inline_cpp! { DoWork(); }
| ^^^^^^^^^^^^^^^^^^^^^^^^^
Unsupported features and syntax limitations
-
Unmatched braces: All
{}braces inside embedded C++ blocks must be properly balanced. If a brace is unmatched (such as in complex preprocessor macros or unclosed string literals), the extraction preprocessor reports an unmatched delimiter error:error: Unmatched delimiter starting at src/lib.rs:10: Context around open brace: inline_cpp! { (int x) -> int { if (x > 0) { return x; }