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

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 signatureRust 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::function is not bound: Crubit does not generate bindings for functions taking or returning std::function. Replace std::function with absl::AnyInvocable in your C++ API, or write a thin C++ wrapper taking absl::AnyInvocable.