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

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++ #include headers, 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:

  1. In a rust_api_from_cpp target (Recommended): When extending the generated Rust bindings of an existing C++ library using custom Rust source files (additional_rust_srcs_for_crubit_bindings).
  2. In a rust_library_with_embedded_cpp target: When authoring a standalone Rust library with embedded C++ without an underlying cc_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. Any inline_cpp! block in the crate can access types declared in global_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:

  1. 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
    
  2. Define helper templates in global_cpp!: Define C++ template helpers at module scope, then instantiate them with concrete types in inline_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; }