Callbacks: absl::AnyInvocable
Crubit supports calling C++ functions that accept or return
absl::AnyInvocable
from Rust.
In Rust, absl::AnyInvocable parameters and return types map to standard Rust
trait objects (Box<dyn Fn...> and Box<dyn FnOnce...>).
For the reverse direction (calling Rust functions that take closures from C++), see C++ bindings for Rust callbacks.
BUILD configuration
To generate Rust bindings for a C++ library that uses absl::AnyInvocable, ensure
the cc_library depends on @abseil-cpp//absl/functional:any_invocable and is opted
into Crubit using rust_api_from_cpp:
cc_library(
name = "cpp_api",
srcs = ["cpp_api.cc"],
hdrs = ["cpp_api.h"],
aspect_hints = [":cpp_api_rust.hint"],
deps = [
"@abseil-cpp//absl/functional:any_invocable",
],
)
rust_api_from_cpp(
name = "cpp_api_rust",
cpp_target = ":cpp_api",
)
rust_test(
name = "user_of_cpp_api",
srcs = ["user_of_cpp_api.rs"],
deps = [
":cpp_api_rust",
"//third_party/gtest_rust/googletest",
],
)
The complete, tested version is in
examples/types/callbacks/BUILD.
Type mapping
Crubit maps absl::AnyInvocable to heap-allocated Rust trait objects:
C++ absl::AnyInvocable signature | Rust binding type |
|---|---|
absl::AnyInvocable<R(Args...) const> | Box<dyn Fn(Args...) -> R + Send + Sync> |
absl::AnyInvocable<R(Args...) &&> | Box<dyn FnOnce(Args...) -> R + Send> |
absl::AnyInvocable<R(Args...)> (non-const) | Box<dyn Fn(Args...) -> R + Send + Sync> |
Non-const absl::AnyInvocable is promoted to Fn
Non-const absl::AnyInvocable<R(Args...)> is mapped to Box<dyn Fn...> (rather than
FnMut) in Rust.
In C++, invoking a non-const absl::AnyInvocable does not require exclusive
mutable access to the function object. Rust’s FnMut, by contrast, requires an
exclusive &mut borrow that would be unsound if called concurrently or re-entrantly
from C++. Mapping to Fn guarantees that the callback is safe to invoke in any
context.
If you need to mutate state across invocations in a Rust closure passed to a non-const
absl::AnyInvocable, use interior mutability types such as Atomic* (e.g. AtomicI32),
Cell, RefCell, or Mutex instead of &mut capture.
Passing callbacks from Rust to C++
C++ functions can accept absl::AnyInvocable by value:
// In cpp_api.h:
#include <cstdint>
#include "third_party/absl/functional/any_invocable.h"
// Accepts a callback that may be called multiple times:
int32_t ApplyMultiplier(absl::AnyInvocable<int32_t(int32_t) const> callback,
int32_t value);
// Accepts a single-use callback:
void InvokeOnce(absl::AnyInvocable<void() &&> callback);
In Rust, pass a boxed closure (Box::new(...)):
#![allow(unused)]
fn main() {
// In user_of_cpp_api.rs:
// 1. Calling a const callback:
let result = cpp_api::ApplyMultiplier(Box::new(|x: i32| x * 2), 21);
assert_eq!(result, 42);
// 2. Calling a single-use (FnOnce) callback:
let data = String::from("resource");
cpp_api::InvokeOnce(Box::new(move || {
println!("Consumed: {data}");
}));
}
Returning callbacks from C++ to Rust
C++ functions can return absl::AnyInvocable:
// In cpp_api.h:
absl::AnyInvocable<int32_t(int32_t) const> MakeAdder();
In Rust, the returned value is a callable trait object that can be called directly:
#![allow(unused)]
fn main() {
let adder = cpp_api::MakeAdder();
assert_eq!(adder(41), 42);
}
Type aliases
Type aliases to absl::AnyInvocable in C++ receive corresponding type alias
bindings in Rust:
using IntMapper = absl::AnyInvocable<int32_t(int32_t) const>;
int32_t ApplyAlias(IntMapper callback, int32_t value);
In Rust, cpp_api::IntMapper is an alias for
Box<dyn Fn(i32) -> i32 + Send + Sync>.
Limitations
std::functionis not bound: Crubit does not generate bindings for functions taking or returningstd::function. Replacestd::functionwithabsl::AnyInvocablein your C++ API, or write a thin C++ wrapper takingabsl::AnyInvocable.