easy_ffi/lib.rs
1//! # Easy-FFI: A helper macro for FFI helper macros
2//!
3//! This crate attempts to make the process of writing an unwind-safe C api more ergonomic.
4//!
5//! # What this crate does
6//!
7//! * Prevents unwinding across the FFI boundary
8//! * Allows the use of the usual Rust error handling idioms
9//!
10//! # What this crate does *not* do
11//!
12//! * Prevent you from dereferencing invalid pointers
13//! * Prevent memory leaks
14//! * Any kind of validation of arguments or returns from your FFI functions
15//!
16//! # Example
17//!
18//! ## Without `easy_ffi`:
19//!
20//! ```
21//! fn thing_that_could_fail_or_panic() -> Result<i32, &'static str> {
22//! // Do stuff...
23//! # Ok(5)
24//! }
25//!
26//! #[no_mangle]
27//! pub extern "C" fn my_ffi_function(i: i32) -> i32 {
28//! // Unwinding over the FFI boundary is UB, so we need to catch panics
29//! let panic_result: Result<i32, _> = ::std::panic::catch_unwind(move || {
30//! let result_one = thing_that_could_fail_or_panic();
31//!
32//! // We need to match on this result to handle the possible Result::Err
33//! // and convert it to a senssible ffi representation.
34//! match result_one {
35//! Ok(actual) => return actual,
36//! Err(e) => {
37//! println!("Oops! {:?}", e);
38//! return -1;
39//! }
40//! }
41//! });
42//!
43//! // Then, we need to match on the catch_unwind result again like we did for the Result::Err
44//! match panic_result {
45//! Ok(actual) => return actual,
46//! Err(_e) => {
47//! println!("unexpected panic!");
48//! return -1;
49//! }
50//! }
51//! }
52//! ```
53//!
54//! Using only rust std, anything that could potentially panic needs to be
55//! wrapped with `catch_unwind` to prevent unwinding into C. Also, since FFI functions
56//! won't be returning Rust's `Result<T, E>`, you're prevented from using `try!` or `?`
57//! for error-handling ergonomics.
58//!
59//! ## With `easy_ffi`:
60//!
61//! ```
62//! # #[macro_use] extern crate easy_ffi;
63//!
64//! fn thing_that_could_fail_or_panic() -> Result<i32, &'static str> {
65//! // Do stuff...
66//! # Ok(5)
67//! }
68//!
69//! // This defines a new macro that will be used to wrap a more "rusty"
70//! // version of our ffi function.
71//! easy_ffi!(my_ffi_fn =>
72//! // Now we define a handler for each of the error cases: A `Result::Err` and
73//! // a caught panic. `Result::Err` comes first:
74//! |err| {
75//! println!("{}", err);
76//! // The handler still needs to return the actual type that the C api expects,
77//! // so we're going to do so here:
78//! -1
79//! }
80//! // Next, the panic. This will have the type `Box<Any + Send + 'static>`. See
81//! // `::std::panic::catch_unwind` for more details.
82//! |panic_val| {
83//! match panic_val.downcast_ref::<&'static str>() {
84//! Some(s) => println!("panic: {}", s),
85//! None => println!("unknown panic!"),
86//! }
87//! // As with the error handler, the panic handler also needs to return
88//! // the real ffi return type.
89//! -1
90//! }
91//! );
92//!
93//! // Using the new macro that `easy_ffi!` created for us, we can write our
94//! // function just like any Rust function that returns a `Result`. This will
95//! // automatically be wrapped in a `catch_unwind`, and error handling will be
96//! // left to the "handler" that was defined in the call to `easy_ffi`.
97//! my_ffi_fn!(
98//! /// You can put doc comments here!
99//! ///
100//! /// This should generate a function with the signature `fn(i32) -> i32`,
101//! /// with all of the necessary `pub`, `#[no_mangle]`, `extern "C"`, etc.
102//! fn foo(i: i32) -> Result<i32, &'static str> {
103//! thing_that_could_fail_or_panic()
104//! }
105//! );
106//! # fn main() {}
107//! ```
108
109#[macro_export]
110macro_rules! easy_ffi {
111 ($name:ident => |$err:ident| $err_body:tt |$panic:ident| $panic_body:tt) => (
112 easy_ffi!(@actual ($) $name $err $err_body $panic $panic_body);
113 );
114 (@actual ($dol:tt) $name:ident $err:ident $err_body:tt $panic:ident $panic_body:tt) => {
115 macro_rules! $name {
116 (
117 $dol (#[$dol attr:meta])*
118 fn $dol fn_name:ident (
119 $dol ($dol arg:ident : $dol arg_ty:ty),* $dol (,)*
120 ) -> Result<$dol ok_ty:ty, $dol err_ty:ty>
121 $dol body:tt
122 ) => (
123 #[no_mangle]
124 $dol (#[$attr])*
125 pub extern "C" fn $fn_name($dol ($arg : $arg_ty),*) -> $ok_ty {
126 let safe_res:
127 ::std::result::Result<$ok_ty, ::std::result::Result<$err_ty, Box<::std::any::Any + Send + 'static>>> =
128 ::std::panic::catch_unwind(move || $body)
129 .map_err(|e| ::std::result::Result::Err(e))
130 .and_then(|ok| ok.map_err(|e| ::std::result::Result::Ok(e)));
131 match safe_res {
132 Ok(x) => return x,
133 Err(Ok($err)) => $err_body,
134 Err(Err($panic)) => $panic_body,
135 }
136 }
137 );
138 }
139 };
140}
141
142#[cfg(test)]
143mod tests {
144 #![allow(private_no_mangle_fns)]
145
146 easy_ffi!(my_ffi_fn =>
147 |err| {
148 println!("{}", err);
149 -1
150 }
151 |panic_val| {
152 match panic_val.downcast_ref::<&'static str>() {
153 Some(s) => println!("panic: {}", s),
154 None => println!("unknown panic!"),
155 };
156 -1
157 }
158 );
159
160 my_ffi_fn! (
161 /// Foo: do stuff
162 fn foo(i: i32) -> Result<i32, &'static str> {
163 match i {
164 5 => panic!("I'm afraid of 5's!"),
165 i if i <= 0 => Err("already <= 0, can't go lower"),
166 i => Ok(i-1),
167 }
168 }
169 );
170
171 #[test]
172 fn it_works() {
173 assert_eq!(-1, foo(5));
174 assert_eq!(-1, foo(0));
175 assert_eq!(0, foo(1));
176 assert_eq!(1, foo(2));
177 }
178}