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:
pub fn add_two_integers(x: i32, y: i32) -> i32 {
x + y
}
Crubit will generate the following function declaration, which calls into accompanying glue code:
::std::int32_t add_two_integers(::std::int32_t x, ::std::int32_t y);
inline ::std::int32_t add_two_integers(::std::int32_t x, ::std::int32_t y) {
return __crubit_internal::__crubit_thunk_add_utwo_uintegers(x, y);
}
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
mutreference 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++:
#![allow(unused)]
fn main() {
fn foo(&self) {}
fn foo(_: &mut i32) {}
fn foo(_: &i32, _: &i32) {}
}
However, none of the below will receive bindings:
#![allow(unused)]
fn main() {
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:
#![allow(unused)]
fn main() {
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:
#![allow(unused)]
fn main() {
fn process_mut_bytes(_: &mut [u8]) {} // Cannot pass string literals or const spans to mutable byte slices
}
&[T]: Maps tors::SliceRef<const T>(crubit/support/rs_std/slice_ref.h). Converts implicitly fromabsl::Span<const T>,absl::Span<T>,std::span<const T>,std::span<T>,std::vector<T>, andstd::array<T, N>.&mut [T]: Maps tors::SliceRef<T>(crubit/support/rs_std/slice_ref.h). Converts implicitly from mutablestd::span<T>andabsl::Span<T>, or construct viars::SliceRef(ptr, size).&str: Maps tors::StrRef(crubit/support/rs_std/str_ref.h). Converts from string literals andstd::string_view.&[u8]: Maps tors::SliceRef<const uint8_t>. In addition toabsl::Spanandstd::span, also converts from string literals andstd::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 constraints | Canonical substitution |
|---|---|
T: AsMut<U> | &mut U |
T: AsRef<U> | &U |
T: Into<U> | U |
F: Fn(Args...) -> R | rs::FnRef<R(Args...) const> |
F: FnMut(Args...) -> R | rs::FnRef<R(Args...)> |
F: FnOnce(Args...) -> R + 'static | rs::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 tors::FnRef<... const>(borrowed, no allocation).&mut dyn FnMut(...)/impl FnMut(...): Maps tors::FnRef<...>(borrowed, no allocation).impl FnOnce(...)(without'static): Maps tors::FnRef<...>(borrowed, no allocation).Box<dyn Fn(...)>: Maps tors::Fn<... const>(owned).Box<dyn FnMut(...)>: Maps tors::Fn<...>(owned mutable).Box<dyn FnOnce(...)>/impl FnOnce(...) + 'static: Maps tors::Fn<... &&>(owned, single-use).
C++ callers can satisfy these parameters by passing C++ lambdas, function
pointers, or absl::AnyInvocable. See Callbacks for full
details.