Skip to content

Document canonical way to pass closures from Rust to C++ #1666

@balintbarna

Description

@balintbarna

What I got to work:

// lib.rs
#[cxx::bridge]
mod ffi {
    #[derive(Debug)]
    enum ClosureArg {
        Yes,
        No,
    }
    extern "Rust" {
        type ClosureBridge;
        fn call(&self, arg: ClosureArg);
    }
    unsafe extern "C++" {
        include!("bridge/cpp/lib.hpp");
        fn receive_closure(callback: Box<ClosureBridge>);
    }
}
pub use ffi::{ClosureArg, receive_closure};
pub struct ClosureBridge(Box<dyn Fn(ClosureArg)>);
impl ClosureBridge {
    fn call(&self, arg: ClosureArg) {
        (self.0)(arg);
    }
}
pub fn pass_closure(callback: impl Fn(ClosureArg) + 'static) {
    receive_closure(Box::new(ClosureBridge(Box::new(callback))));
}
// test.rs
use bridge::pass_closure;
fn main() {
    let x = 1;
    pass_closure(move |arg| {
        println!("Closure {x} called with arg: {arg:?}");
    })
}
// lib.hpp
#pragma once
#include "rust/cxx.h"
#include "bridge/lib.rs.h"
void receive_closure(rust::Box<ClosureBridge> closure) {
   closure->call(ClosureArg::Yes);
}

This is very verbose, has a lot of scaffolding functions that are annoying to debug when compilation fails, and includes double boxing (box of Fn inside box of helper class). Is there a better way?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions