Module ffi_helpers::task [] [src]

Management of asynchronous tasks in an FFI context.

The Task API is very similar to the that exposed by Rust futures, with the aim to be usable from other languages.

The main idea is your C users will:

  1. Create a Task struct (who's run() method will execute the job)
  2. Spawn the Task on a background thread, receiving an opaque TaskHandle through which the task can be monitored
  3. Periodically poll() the TaskHandle to see whether it's done or if there was an error
  4. Retrieve the result when the Task completes
  5. destroy the original TaskHandle when it's longer need it

Implementing The Task API

To use the Task API you just need to create a struct which implements the Task trait. This is essentially just a trait with a run() function that'll be given a CancellationToken.

Implementors of the Task trait should periodically check the provided CancellationToken to see whether the caller wants them to stop early.


Once you have a type implementing Task you can use the export_task!() macro to generate extern "C" functions for spawning the task and monitoring its progress. This is usually the most annoying/error-prone/tedious part of exposing running a Task in the background using just a C API.

For this example we're defining a Spin task which will count up until it receives a cancel signal, then return the number of spins.

#[derive(Debug, Clone, Copy)]
pub struct Spin;

impl Task for Spin {
    type Output = usize;

    fn run(&self, cancel_tok: &CancellationToken) -> Result<Self::Output, Error> {
        let mut spins = 0;

        while !cancel_tok.cancelled() {
            spins += 1;


// Generate the various `extern "C"` utility functions for working with the
// `Spin` task. The `spawn` function will be called `spin_spawn`, and so on.
export_task! {
    Task: Spin;
    spawn: spin_spawn;
    wait: spin_wait;
    poll: spin_poll;
    cancel: spin_cancel;
    cancelled: spin_cancelled;
    handle_destroy: spin_handle_destroy;
    result_destroy: spin_result_destroy;

fn main() {
    // create our `Spin` task
    let s = Spin;

    unsafe {
        // spawn the task in the background and get a handle to it
        let handle = spin_spawn(&s);
        assert_eq!(spin_cancelled(handle), 0,
            "The spin shouldn't have been cancelled yet");

        // poll the task. The result can vary depending on the outcome:
        // - If the task completed, get a pointer to the `Output`
        // - If it completed with an error, return `null` and update the
        //   LAST_ERROR appropriately
        // - Return `null` and *don't* set LAST_ERROR if the task isn't done
        let ret = spin_poll(handle);
        assert_eq!(last_error_length(), 0, "There shouldn't have been any errors");
        assert!(ret.is_null(), "The task should still be running");

        // tell the task to stop spinning by sending the cancel signal

        // wait for the task to finish and retrieve a pointer to its result
        // Note: this will automatically free the handle, so we don't need
        //       to manually call `spin_handle_destroy()`.
        let got = spin_wait(handle);

        assert_eq!(last_error_length(), 0, "There shouldn't have been any errors");
        assert!(!got.is_null(), "Oops!");

        let num_spins: usize = *got;

        // don't forget the result is heap allocated so we need to free it

Managing Task Output Lifetimes

The result of a Task will be allocated on the heap and then a pointer returned to the user from the poll and wait functions. It is the caller's responsibility to ensure this gets free'd once you're done with it.

The export_task!() macro lets you define a results_destroy function which will free the object for you.

Zero-sized types (like () - Rust's equivalent of the C void or Python None) won't incur an allocation, meaning the results_destroy function will be a noop.



A shareable token to let you notify other tasks they should stop what they are doing and exit early.


An error to indicate a task was cancelled.


An opaque handle to some task which is running in the background.



A cancellable task which is meant to be run in a background thread.