[rust-dev] Writing callbacks from C into Rust

Brian Anderson banderson at mozilla.com
Tue Feb 14 14:18:55 PST 2012


We now have a long-desired mechanism for calling from C back into Rust functions. This should make writing bindings for certain libraries much easier. The short version is that we have a new type of function declaration, a 'crust' function ("C-to-Rust" - cute, or obnoxious?) that follows the C ABI and can be used as a callback from C functions. It looks like this

    crust fn cb(data: ctypes::uintptr_t) -> ctypes::uintptr_t {
        ... do whatever rusty stuff you like ...

These are special in several ways

    * They cannot be called from Rust code
    * Their value can be taken, but they always have type *u8
    * A task in a crust callback that fails results in the runtime aborting abrubtly
    * Tasks cannot be killed while in a crust callback

Their use should generally be encapsulated into libraries that can deal with their special nature. My general suggestion for using them successfully is:

    * Use them primarily to dispatch messages to other tasks to avoid catastrophic failure
    * If you are using them from an event loop then first put yourself into a new, single-threaded scheduler using spawn_sched to avoid blocking other tasks

Additionally, the inability to kill tasks running in callbacks places an additional large burden on those using callbacks to handle long-running event loops (like libuv) - if the runtime is failing you have to ensure that your callbacks can detect this situation and exit cleanly. Otherwise your task will keep the runtime alive after all other tasks have died.

My current suggestion is to have a monitor task (on a different scheduler) that detects failure and notifies the event loop callbacks via a message that it needs to terminate (using the non-blocking comm::peek function to look into the message queue). With libuv we could also inject a cleanup function into the event loop with the async_t handle.

Anyway, here's an example of running the uv event loop using no C code:

use std;

// A selection from the uv API, reexported from the runtime with a rust_ prefix.
#[link_name = "rustrt"]
native mod uv {
    fn rust_uv_loop_new() -> *loop_t;
    fn rust_uv_loop_delete(loop: *loop_t);
    fn rust_uv_default_loop() -> *loop_t;
    fn rust_uv_run(loop: *loop_t) -> ctypes::c_int;
    fn rust_uv_unref(loop: *loop_t);
    fn rust_uv_idle_init(loop: *loop_t, idle: *idle_t) -> ctypes::c_int;
    fn rust_uv_idle_start(idle: *idle_t, cb: idle_cb) -> ctypes::c_int;

type opaque_cb = *u8;

type handle_type = ctypes::enum;

type close_cb = opaque_cb;
type idle_cb = opaque_cb;

// Redefine various uv structs. Most fields we don't care about
type handle_private_fields = {
    a00: ctypes::c_int, a01: ctypes::c_int,
    a02: ctypes::c_int, a03: ctypes::c_int,
    a04: ctypes::c_int, a05: ctypes::c_int,
    a06: int, a07: int,
    a08: int, a09: int,
    a10: int, a11: int,
    a12: int

type handle_fields = {
    loop: *loop_t,
    type_: handle_type,
    close_cb: close_cb,
    mutable data: *ctypes::void,
    private: handle_private_fields

type handle_t = {
    fields: handle_fields

type loop_t = int;

type idle_t = {
    fields: handle_fields
    /* private: idle_private_fields */

fn handle_fields_new() -> handle_fields {
        loop: ptr::null(),
        type_: 0u32,
        close_cb: ptr::null(),
        mutable data: ptr::null(),
        private: {
            a00: 0i32, a01: 0i32, a02: 0i32, a03: 0i32,
            a04: 0i32, a05: 0i32,
            a06: 0, a07: 0, a08: 0, a09: 0,
            a10: 0, a11: 0, a12: 0

fn idle_new() -> idle_t {
        fields: handle_fields_new()

// The C ABI callback function. When this is called it will
// automatically switch to the Rust stack, and we can run arbitrary
// Rust code (but we must not fail!).
crust fn idle_cb(handle: *ctypes::void,
                 _status: ctypes::c_int) unsafe {

    std::io::println("I'm a-callin' back");

    let idle: *idle_t = unsafe::reinterpret_cast(handle);

    // Drop the reference count of the event loop so
    // that it exits

fn main() unsafe {

    // Put ourselves into a new scheduler so that we
    // run the native event loop without blocking other tasks
    task::spawn_sched(1u) {||

        let loop = uv::rust_uv_loop_new();
        let h = idle_new();

        uv::rust_uv_idle_init(loop, ptr::addr_of(h));

        // Take the value of our callback function
        let callback = idle_cb;
        uv::rust_uv_idle_start(ptr::addr_of(h), callback);

        // Begin the event loop

More information about the Rust-dev mailing list