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}