Rust bindings for C++ functions
Rust code can call (non-member) functions defined in C++, provided that the parameter and return types are supported by Crubit:
- If a parameter or return type is a primitive type, then the bindings for the function use the corresponding Rust type.
- Similarly, if a parameter or return type is a pointer type, then the bindings for the function use the corresponding Rust pointer type.
- If a parameter or return type is a C++ reference type, then the bindings for the function use a Rust reference (if the lifetime was either explicitly specified or elided and inferred) or a Rust pointer (otherwise). See References and lifetimes.
- If the type is a user-defined type, such as a class type or enum, then the bindings for the function use the bindings for that type.
Additionally, code can call member functions defined in C++ if the parameter and return types are supported by Crubit (see above). Non-static member functions become Rust methods, and static member functions become associated functions.
Examples
Functions
Given the following C++ header:
inline int32_t add_two_integers(int32_t x, int32_t y) { return x + y; }
Crubit will generate the following bindings, with a safe public function that calls into the corresponding FFI glue:
#[inline(always)]
pub fn add_two_integers(x: i32, y: i32) -> i32 {
unsafe { crate::detail::__rust_thunk___ZN5gshoe16add_two_integersEii(x, y) }
}
Which can be used like this:
fn main() {
let sum = example_lib::gshoe::add_two_integers(2, 2);
println!("sum = {sum}");
}
Methods
Given the following C++ header:
struct Bar {
int x;
void MyMethod() {};
};
Crubit will generate the following bindings:
#[derive(Clone, Copy, ::ctor::MoveAndAssignViaCopy)]
#[cfi_encoding = "N3foo3BarE"]
#[repr(C)]
///CRUBIT_ANNOTATE: cpp_type=foo :: Bar
///CRUBIT_ANNOTATE: cpp_move_constructible=
pub struct Bar {
pub x: ::ffi_11::c_int,
}
impl Bar {
#[inline(always)]
pub fn MyMethod<'__this>(&'__this mut self) {
unsafe { self::bar::MyMethod(self) }
}
}
Which can be used like this:
fn main() {
let mut x = Bar { x: 1 };
x.MyMethod();
}
The this parameter of a method becomes &self or &mut self. See References
and lifetimes for more details.
unsafe functions
Which C++ functions are marked unsafe in Rust?
By default, the Rust binding to a C++ function is marked as safe or unsafe
based on the types of its parameters. If a C++ function accepts only simple
types like integers, the resulting Rust binding will be marked as safe.
Functions which accept a raw pointer are automatically marked as unsafe.
This behavior can be overridden using the CRUBIT_UNSAFE,
CRUBIT_UNSAFE_MARK_SAFE and CRUBIT_OVERRIDE_UNSAFE(is_unsafe) macros.
For example, given the following C++ header:
inline void SafeSignatureWithoutAnnotation() {}
CRUBIT_UNSAFE inline void SafeSignatureButAnnotatedUnsafe() {}
inline void UnsafeSignatureWithoutAnnotation(void*) {}
CRUBIT_UNSAFE_MARK_SAFE inline void UnsafeSignatureButAnnotatedSafe(void*) {}
CRUBIT_OVERRIDE_UNSAFE(/*is_unsafe=*/false) inline void SafeBasedOnBoolean() {}
CRUBIT_OVERRIDE_UNSAFE(/*is_unsafe=*/true) inline void UnsafeBasedOnBoolean() {}
Crubit will generate the following bindings:
#[inline(always)]
pub fn SafeSignatureWithoutAnnotation() {
unsafe { crate::detail::__rust_thunk___Z30SafeSignatureWithoutAnnotationv() }
}
#[inline(always)]
pub unsafe fn SafeSignatureButAnnotatedUnsafe() {
unsafe { crate::detail::__rust_thunk___Z31SafeSignatureButAnnotatedUnsafev() }
}
#[inline(always)]
pub unsafe fn UnsafeSignatureWithoutAnnotation(__param_0: *mut ::ffi_11::c_void) {
unsafe { crate::detail::__rust_thunk___Z32UnsafeSignatureWithoutAnnotationPv(__param_0) }
}
#[inline(always)]
pub fn UnsafeSignatureButAnnotatedSafe(__param_0: *mut ::ffi_11::c_void) {
unsafe { crate::detail::__rust_thunk___Z31UnsafeSignatureButAnnotatedSafePv(__param_0) }
}
#[inline(always)]
pub fn SafeBasedOnBoolean() {
unsafe { crate::detail::__rust_thunk___Z18SafeBasedOnBooleanv() }
}
#[inline(always)]
pub unsafe fn UnsafeBasedOnBoolean() {
unsafe { crate::detail::__rust_thunk___Z20UnsafeBasedOnBooleanv() }
}
mod detail {
#[allow(unused_imports)]
use super::*;
unsafe extern "C" {
pub(crate) unsafe fn __rust_thunk___Z30SafeSignatureWithoutAnnotationv();
pub(crate) unsafe fn __rust_thunk___Z31SafeSignatureButAnnotatedUnsafev();
pub(crate) unsafe fn __rust_thunk___Z32UnsafeSignatureWithoutAnnotationPv(
__param_0: *mut ::ffi_11::c_void,
);
pub(crate) unsafe fn __rust_thunk___Z31UnsafeSignatureButAnnotatedSafePv(
__param_0: *mut ::ffi_11::c_void,
);
pub(crate) unsafe fn __rust_thunk___Z18SafeBasedOnBooleanv();
pub(crate) unsafe fn __rust_thunk___Z20UnsafeBasedOnBooleanv();
}
}
Which can be used like this:
#[deny(unused_unsafe)]
fn main() {
// Can safely invoke functions that accept simple types or are marked safe.
example_lib::SafeSignatureWithoutAnnotation();
example_lib::UnsafeSignatureButAnnotatedSafe(core::ptr::null_mut());
example_lib::SafeBasedOnBoolean();
// `unsafe` is needed to invoke functions that accept raw pointers or are marked
// unsafe.
unsafe {
example_lib::UnsafeSignatureWithoutAnnotation(core::ptr::null_mut());
example_lib::SafeSignatureButAnnotatedUnsafe();
example_lib::UnsafeBasedOnBoolean();
}
}
Correct usage of unsafe
Functions marked unsafe cannot be called outside of an unsafe block. In
order to avoid undefined behavior when using unsafe, callers must:
-
Ensure that the pointer being passed to C++ is a valid C++ pointer. In particular, it must not be dangling (e.g.
Nonnull::dangling()). -
Ensure that the safety conditions documented in C++ are upheld. For example, if the C++ function accepts a reference or non-null pointer, then do not pass in
0 as *const _.
Soundness
Note that many “safe” C++ functions may still trigger undefined behavior if used
incorrectly. Regardless of whether a C++ function is marked as unsafe, calls
into C++ will only be memory-safe if the caller verifies that all function
preconditions are met.
Function Visibility: pub(crate)
By default, bindings for C++ functions and methods are generated with pub
visibility in Rust.
You can restrict the visibility of a function or method to the generated crate
using CRUBIT_PUB_CRATE from "support/annotations.h":
#include "support/annotations.h"
CRUBIT_PUB_CRATE void HelperFunction();
class MyClass {
public:
CRUBIT_PUB_CRATE void InternalMethod();
};
This is especially useful when providing handwritten Rust APIs via the srcs
attribute of rust_api_from_cpp (see Best
Practices). The
custom Rust code in srcs can call the pub(crate) functions or methods, while
keeping them hidden from downstream crates.
CRUBIT_PUB_CRATE is only valid on functions and methods; applying it to types
will produce an error.
Function Attributes
Most function attributes are not supported. For example, functions marked
[[noreturn]] don’t get bindings. [[nodiscard]] is supported and becomes
#[must_use].