Crate easy_ffi [] [src]

Easy-FFI: A helper macro for FFI helper macros

This crate attempts to make the process of writing an unwind-safe C api more ergonomic.

What this crate does

  • Prevents unwinding across the FFI boundary
  • Allows the use of the usual Rust error handling idioms

What this crate does not do

  • Prevent you from dereferencing invalid pointers
  • Prevent memory leaks
  • Any kind of validation of arguments or returns from your FFI functions

Example

Without easy_ffi:

fn thing_that_could_fail_or_panic() -> Result<i32, &'static str> {
    // Do stuff...
}

#[no_mangle]
pub extern "C" fn my_ffi_function(i: i32) -> i32 {
    // Unwinding over the FFI boundary is UB, so we need to catch panics
    let panic_result: Result<i32, _> = ::std::panic::catch_unwind(move || {
        let result_one = thing_that_could_fail_or_panic();

        // We need to match on this result to handle the possible Result::Err
        // and convert it to a senssible ffi representation.
        match result_one {
            Ok(actual) => return actual,
            Err(e) => {
                println!("Oops! {:?}", e);
                return -1;
            }
        }
    });

    // Then, we need to match on the catch_unwind result again like we did for the Result::Err
    match panic_result {
        Ok(actual) => return actual,
        Err(_e) => {
            println!("unexpected panic!");
            return -1;
        }
    }
}

Using only rust std, anything that could potentially panic needs to be wrapped with catch_unwind to prevent unwinding into C. Also, since FFI functions won't be returning Rust's Result<T, E>, you're prevented from using try! or ? for error-handling ergonomics.

With easy_ffi:

fn thing_that_could_fail_or_panic() -> Result<i32, &'static str> {
    // Do stuff...
}

// This defines a new macro that will be used to wrap a more "rusty"
// version of our ffi function.
easy_ffi!(my_ffi_fn =>
    // Now we define a handler for each of the error cases: A `Result::Err` and
    // a caught panic. `Result::Err` comes first:
    |err| {
        println!("{}", err);
        // The handler still needs to return the actual type that the C api expects,
        // so we're going to do so here:
        -1
    }
    // Next, the panic. This will have the type `Box<Any + Send + 'static>`. See
    // `::std::panic::catch_unwind` for more details.
    |panic_val| {
        match panic_val.downcast_ref::<&'static str>() {
            Some(s) => println!("panic: {}", s),
            None => println!("unknown panic!"),
        }
        // As with the error handler, the panic handler also needs to return
        // the real ffi return type.
        -1
    }
);

// Using the new macro that `easy_ffi!` created for us, we can write our
// function just like any Rust function that returns a `Result`. This will
// automatically be wrapped in a `catch_unwind`, and error handling will be
// left to the "handler" that was defined in the call to `easy_ffi`.
my_ffi_fn!(
    /// You can put doc comments here!
    ///
    /// This should generate a function with the signature `fn(i32) -> i32`,
    /// with all of the necessary `pub`, `#[no_mangle]`, `extern "C"`, etc.
    fn foo(i: i32) -> Result<i32, &'static str> {
        thing_that_could_fail_or_panic()
    }
);

Macros

easy_ffi