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++ 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].