2

I have some state struct in Rust and a C library that will accept and call an extern "C" fn.

fn get_callback(state: State) -> extern "C" fn ... {
    extern "C" fn callback(args: &[Whatever]) -> Something {
        // I need to use that state here
    }

    callback
}

Of course, this doesn't work because callback gets defined outside get_callback like any other C function.

How can I have some particular state inside of the callback? I need this to add mruby callbacks to Rust and using a global variable for the state is not desirable since every mruby state has its own variable.

dragostis
  • 2,574
  • 2
  • 20
  • 39

1 Answers1

2

You can only do this if your callback accepts some "user data" argument which is injected by the calling side and set when this callback is configured, e.g. if you have an API like this:

type Callback = extern fn(*mut c_void);
extern {
    fn register_callback(callback: Callback, user_data: *mut c_void);
}

If this does not hold for your C API then there is no way to do it without some global state!

You can provide a closure to this callback like this:

fn register<F: FnMut() + 'static>(cb: F) {
    extern fn internal_callback(user_data: *mut c_void) {
        let callback = user_data as *mut Box<FnMut()>;
        let callback = unsafe { &mut *callback };
        callback();
    }

    let cb: Box<Box<FnMut()>> = Box::new(Box::new(cb));
    let cb_raw = Box::into_raw(cb) as *mut c_void;

    unsafe {
        register_callback(internal_callback, cb_raw);
    }
}

It probably makes sense to use recover() to protect against panics crossing language boundary, but I omitted it for the sake of simplicity.

The above API has a problem, though: it allows callback environments to leak. I see no general solution for it; it heavily depends on your API.

For example, you may have a function which only uses the passed callback only during its execution, i.e. there is no need to store the callback state anywhere. It may look like this:

type Callback = extern fn(i32, *mut c_void);
extern {
    fn c_do_something(arg: i32, user_data: *mut c_void, callback: Callback);
}

Then it may be used like this:

fn do_something<F: FnMut(i32)>(arg: i32, mut cb: F) {
    extern fn internal_callback(arg: i32, user_data: *mut c_void) {
        let callback = user_data as *mut &mut FnMut(i32);
        let callback = unsafe { &mut *user_data };
        callback(arg);
    }
    let cb: &mut &mut FnMut = &mut &mut cb;
    let cb_raw = cb as *mut _ as *mut c_void;

    unsafe {
        c_do_something(arg, cb_raw, internal_callback);
    }
}

In both these approaches we set up a proxy callback function which accepts the user data argument and interprets it as a pointer to the closure environment. BTW, maybe it is possible to make internal_callback() generic and avoid creating trait objects out of the closures, but this does not change the whole picture much.

If your C API works more like in the first example (registering a callback), then you can probably store your callbacks inside some structure, passing only pointers into this structure to the C side. Then you need to make extra sure that the C side won't call these callbacks after your structure is dropped - after you convert references to your callbacks to raw pointers, the lifetime link from the callback use site to their definition will be severed, and you become responsible for enforcing the lifetime constraints. This may be okay if your callbacks are invoked by some entity which has limited lifetime on the C side as well. Then you can link its lifetime with the lifetime of the structure holding closures by designing the API carefully.

And finally, in a dire situation when you have global callbacks which may change frequently, your only choice is to allocate a global state for your callbacks, one piece for each callback, which would store the "current" callback. Then you need your Rust wrappers for registration functions replace the old callback with the new one. It could look like this:

type Callback = extern fn(*mut c_void);
extern {
    fn register_callback(callback: Callback, user_data: *mut c_void);
}

fn register<F: FnMut() + 'static>(cb: F) {
    extern fn internal_callback(user_data: *mut c_void) {
        let callback = user_data as *mut Mutex<Option<Box<Box<FnMut()>>>>;
        let callback = unsafe { &mut *callback };
        let callback = callback.lock();
        if let Some(callback) = callback.as_mut() {
            callback();
        }
    }
    lazy_static! {
        static ref CURRENT_CALLBACK: Mutex<Option<Box<Box<FnMut() + 'static>>>> = Mutex::new(None);
    }
    let cb: Box<Box<FnMut()>> = Box::new(Box::new(cb));
    // extract the old callback and destroy it, if needed
    mem::replace(&mut *CURRENT_CALLBACK.lock(), Some(cb));
    let cb_raw = &mut *CURRENT_CALLBACK as *mut _;

    unsafe {
        register_callback(internal_callback, cb_raw);
    }
}

Here the inner wrapper is somewhat more complex; it takes care of synchronizing access to the callback state and only allows one callback to be set up at any single time, cleaning up the old callback when necessary. However, the C side must not use the old callback pointer when register_callback() is called with a new pointer, otherwise things will break.

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • Any reason this question isn't a duplicate of the one [you already answered](http://stackoverflow.com/q/32270030/155423)? – Shepmaster Feb 19 '16 at 15:34
  • @Shepmaster only that I forgot that there was such a question and an answer :) – Vladimir Matveev Feb 19 '16 at 17:19
  • I remember *everything* ;-) – Shepmaster Feb 19 '16 at 17:21
  • @Shepmaster This is not really a duplicate. Only part of this answer is what I was searching for and is not available in the other one. (global state part) Anyway, I've been able to solve this by finding a place to send some user data. :) – dragostis Feb 19 '16 at 18:41