How do I convert a Rust closure to a C-style callback?

You cannot do it unless the C API allows passing a user-provided callback parameter. If it does not, you can only use static functions.

The reason is that closures are not “just” functions. As their name implies, closures “close over” variables from their lexical scope. Each closure has an associated piece of data which holds either values of captured variables (if the move keyword is used) or references to them. This data can be thought of as some unnamed, anonymous struct.

The compiler automatically adds an implementation of the corresponding Fn* traits for these anonymous structs. As you can see, methods on these traits accept self in addition to the closure arguments. In this context, self is the struct on which the trait is implemented. This means that each function which corresponds to a closure also has an additional parameter which contains the closure environment.

If your C API only allows you to pass functions without any user-defined parameters, you cannot write a wrapper which would allow you to use closures. I guess it may be possible to write some global holder for the closures environment, but I doubt it would be easy and safe.

If your C API does allow passing a user-defined argument, then it is possible to do what you want with trait objects:

extern crate libc;

use std::mem;

use libc::{c_int, c_void};

extern "C" {
    fn do_something(f: Option<extern "C" fn(x: c_int, arg: *mut c_void) -> c_int>, arg: *mut c_void) -> c_int;
}

extern "C" fn do_something_handler(x: c_int, arg: *mut c_void) -> c_int {
    let closure: &mut &mut dyn FnMut(i32) -> bool = unsafe { mem::transmute(arg) };
    closure(x as i32) as c_int
}

pub fn do_with_callback<F>(x: i32, mut callback: F) -> bool
    where F: FnMut(i32) -> bool
{
    // reason for double indirection is described below
    let mut cb: &mut dyn FnMut(i32) -> bool = &mut callback;
    let cb = &mut cb;
    unsafe { do_something(Some(do_something_handler), cb as *mut _ as *mut c_void) > 0 }
}

This will only work if do_something does not store the pointer to the callback somewhere. If it does, you need to use a Box<Fn(..) -> ..> trait object and leak it after you pass it to the function. Then, if possible, it should be obtained back from your C library and disposed of. It could look like this:

extern crate libc;

use std::mem;

use libc::{c_int, c_void};

extern "C" {
    fn set_handler(f: Option<extern "C" fn(x: c_int, arg: *mut c_void) -> c_int>, arg: *mut c_void);
    fn invoke_handler(x: c_int) -> c_int;
    fn unset_handler() -> *mut c_void;
}

extern "C" fn do_something_handler(x: c_int, arg: *mut c_void) -> c_int {
    let closure: &mut Box<dyn FnMut(i32) -> bool> = unsafe { mem::transmute(arg) };
    closure(x as i32) as c_int
}

pub fn set_callback<F>(callback: F)
    where F: FnMut(i32) -> bool,
          F: 'static
{
    let cb: Box<Box<dyn FnMut(i32) -> bool>> = Box::new(Box::new(callback));
    unsafe {
        set_handler(Some(do_something_handler), Box::into_raw(cb) as *mut _);
    }
}

pub fn invoke_callback(x: i32) -> bool {
    unsafe { invoke_handler(x as c_int) > 0 }
}

pub fn unset_callback() {
    let ptr = unsafe { unset_handler() };
    // drop the callback
    let _: Box<Box<dyn FnMut(i32) -> bool>> = unsafe { Box::from_raw(ptr as *mut _) };
}

fn main() {
    let mut y = 0;
    set_callback(move |x| {
        y += 1;
        x > y
    });

    println!("First: {}", invoke_callback(2));
    println!("Second: {}", invoke_callback(2));

    unset_callback();
}

Double indirection (i.e. Box<Box<...>>) is necessary because Box<Fn(..) -> ..> is a trait object and therefore a fat pointer, incompatible with *mut c_void because of different size.

Leave a Comment