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 |