1 unstable release
Uses old Rust 2015
0.1.0 | Feb 25, 2018 |
---|
#113 in #macros
9KB
65 lines
An unwind-safe ergonomic ffi wrapper generator for Rust
See the docs for more details.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
lib.rs
:
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()
}
);