C++ bindings for Rust functions

C++ code can call functions defined in Rust, provided that the parameter and return types are supported by Crubit:

  • If a parameter or return type is a fundamental type, then the bindings for the function use the corresponding C++ type.
  • Similarly, if a parameter or return type is a pointer type, then the bindings for the function use the corresponding C++ pointer type.
  • If the type is a user-defined type, such as a struct or enum, then the bindings for the function use the bindings for that type.

As a special case, functions also support reference parameters to supported types, with some restrictions to ensure safety. See References.

Example

Given the following Rust crate:

cs/file:examples/rust/function/example.rs function:add_two_integers

Crubit will generate the following function declaration, which calls into accompanying glue code:

cs/file:examples/rust/function/example_generated.h function:add_two_integers

unsafe functions

C++ does not have an unsafe marker at this time. In the future, Crubit may introduce a way to mark unsafe functions to help increase the reliability of C++ callers.

References

In general, Rust references are not exposed to C++. However, some Rust functions which accept reference parameters do get mapped to C++ functions accepting C++ references:

  • All references must have an unbound parameter lifetime – not 'static, for example.
  • Only the parameter itself can be a reference type. References to references, vectors of references, etc. are still unsupported.
  • If there is a mut reference parameter, it is the only reference parameter.

This set of rules is intended to describe a safe subset of Rust functions, which do not introduce substantial aliasing risk to a mixed C++/Rust codebase.

For example, the following Rust functions will receive C++ bindings, and can be called from C++:

fn foo(&self) {}
fn foo(_: &mut i32) {}
fn foo(_: &i32, _: &i32) {}

However, none of the below will receive bindings:

fn foo(_: &'static i32) {}  // 'static lifetime is bound
fn foo(_: &&i32) {}  // Reference in non-parameter type
fn foo(_: &mut i32, _: &i32) {}  // More than one reference, one of which is mut
fn foo(_: &'a i32) {}  // 'a is not a lifetime parameter of `foo`

Returned references are mapped to C++ references (for self borrows) or C++ pointers, depending on the lifetime.

If you wish to accept more than one reference/pointer in C++, a raw pointer (*const T, *mut T) can be used instead. However, all of the usual unsafe caveats apply.

Slice parameters

Rust slice parameters (&[T], &mut [T], &str) map to rs::SliceRef<const T>, rs::SliceRef<T>, and rs::StrRef in C++.

C++ callers can pass absl::Span<const T>, std::span<const T>, or contiguous containers like std::vector<T> and std::array<T, N> directly to satisfy &[T] slice parameters with no conversion boilerplate:

fn process_text(_: &str) {}     // C++ callers can pass "hello" or std::string_view
fn process_bytes(_: &[u8]) {}   // C++ callers can pass absl::Span<const uint8_t>, std::span, "hello", or std::string_view
fn process_items(_: &[i32]) {}  // C++ callers can pass absl::Span<const int32_t>, std::span, std::vector, etc.

However, mutable slices cannot be implicitly constructed from read-only views or string literals:

fn process_mut_bytes(_: &mut [u8]) {}  // Cannot pass string literals or const spans to mutable byte slices
  • &[T]: Maps to rs::SliceRef<const T> (crubit/support/rs_std/slice_ref.h). Converts implicitly from absl::Span<const T>, absl::Span<T>, std::span<const T>, std::span<T>, std::vector<T>, and std::array<T, N>.
  • &mut [T]: Maps to rs::SliceRef<T> (crubit/support/rs_std/slice_ref.h). Converts implicitly from mutable std::span<T> and absl::Span<T>, or construct via rs::SliceRef(ptr, size).
  • &str: Maps to rs::StrRef (crubit/support/rs_std/str_ref.h). Converts from string literals and std::string_view.
  • &[u8]: Maps to rs::SliceRef<const uint8_t>. In addition to absl::Span and std::span, also converts from string literals and std::string_view.

Generic functions

Crubit doesn't support generating bindings for arbitrary generic functions. Nevertheless, a small set of generic type parameters is supported by using canonical substitutions which are defined as follows:

Generic constraintsCanonical substitution
T: AsMut<U>&mut U
T: AsRef<U>&U
T: Into<U>U
F: Fn(Args...) -> Rrs::FnRef<R(Args...) const>
F: FnMut(Args...) -> Rrs::FnRef<R(Args...)>
F: FnOnce(Args...) -> R + 'staticrs::Fn<R(Args...) &&>

Callbacks (Fn, FnMut, FnOnce)

Rust functions can accept callbacks as borrowed trait objects (&dyn Fn, &mut dyn FnMut), owned boxes (Box<dyn Fn>, Box<dyn FnMut>, Box<dyn FnOnce>), or generic impl Trait parameters:

  • &dyn Fn(...) / impl Fn(...): Maps to rs::FnRef<... const> (borrowed, no allocation).
  • &mut dyn FnMut(...) / impl FnMut(...): Maps to rs::FnRef<...> (borrowed, no allocation).
  • impl FnOnce(...) (without 'static): Maps to rs::FnRef<...> (borrowed, no allocation).
  • Box<dyn Fn(...)>: Maps to rs::Fn<... const> (owned).
  • Box<dyn FnMut(...)>: Maps to rs::Fn<...> (owned mutable).
  • Box<dyn FnOnce(...)> / impl FnOnce(...) + 'static: Maps to rs::Fn<... &&> (owned, single-use).

C++ callers can satisfy these parameters by passing C++ lambdas, function pointers, or absl::AnyInvocable. See Callbacks for full details.