async_ffi/lib.rs
1//! # FFI-compatible [`Future`][`std::future::Future`]s
2//!
3//! Rust currently doesn't provide stable ABI nor stable layout of related structs like
4//! `dyn Future` or `Waker`.
5//! With this crate, we can wrap async blocks or async functions to make a `Future` FFI-safe.
6//!
7//! [`FfiFuture`] provides the same functionality as `Box<dyn Future<Output = T> + Send>` but
8//! it's FFI-compatible, aka. `repr(C)`. Any `Future<Output = T> + Send + 'static` can be converted
9//! into [`FfiFuture`] by calling [`into_ffi`][`FutureExt::into_ffi`] on it, after `use`ing the
10//! trait [`FutureExt`].
11//!
12//! [`FfiFuture`] implements `Future<Output = T> + Send`. You can `await` a [`FfiFuture`] just like
13//! a normal `Future` to wait and get the output.
14//!
15//! For non-[`Send`] or non-`'static` futures, see the section
16//! [Variants of `FfiFuture`](#variants-of-ffifuture) below.
17//!
18//! ## Examples
19//!
20//! Provide some async functions in library: (plugin side)
21//! ```
22//! # async fn do_some_io(_: u32) -> u32 { 0 }
23//! # async fn do_some_sleep(_: u32) {}
24//! // Compile with `crate-type = ["cdylib"]`.
25//! use async_ffi::{FfiFuture, FutureExt};
26//!
27//! #[no_mangle]
28//! pub extern "C" fn work(arg: u32) -> FfiFuture<u32> {
29//! async move {
30//! let ret = do_some_io(arg).await;
31//! do_some_sleep(42).await;
32//! ret
33//! }
34//! .into_ffi()
35//! }
36//! ```
37//!
38//! Execute async functions from external library: (host or executor side)
39//! ```
40//! use async_ffi::{FfiFuture, FutureExt};
41//!
42//! // #[link(name = "myplugin...")]
43//! extern "C" {
44//! #[no_mangle]
45//! fn work(arg: u32) -> FfiFuture<u32>;
46//! }
47//!
48//! async fn run_work(arg: u32) -> u32 {
49//! unsafe { work(arg).await }
50//! }
51//! ```
52//!
53//! ## Proc-macro helpers
54//! If you enable the feature `macros` (disabled by default), an attribute-like procedural macro
55#![cfg_attr(not(feature = "macros"), doc = r"`async_ffi`")]
56#![cfg_attr(feature = "macros", doc = r"[`async_ffi`]")]
57//! is available at top-level. See its own documentation for details.
58//!
59//! With the macro, the example above can be simplified to:
60//! ```
61//! # #[cfg(feature = "macros")] {
62//! # async fn do_some_io(_: u32) -> u32 { 0 }
63//! # async fn do_some_sleep(_: u32) {}
64//! use async_ffi::async_ffi;
65//!
66//! #[no_mangle]
67//! #[async_ffi]
68//! pub async extern "C" fn work(arg: u32) -> u32 {
69//! let ret = do_some_io(arg).await;
70//! do_some_sleep(42).await;
71//! ret
72//! }
73//! # }
74//! ```
75//!
76//! ## Panics
77//!
78//! You should know that
79//! [unwinding across an FFI boundary is Undefined Behaviour](https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-panics).
80//!
81//! ### Panic in `Future::poll`
82//!
83//! Since the body of `async fn` is translated to [`Future::poll`] by the compiler, the `poll`
84//! method is likely to panic. If this happen, the wrapped [`FfiFuture`] will catch unwinding
85//! with [`std::panic::catch_unwind`], returning [`FfiPoll::Panicked`] to cross the FFI boundary.
86//! And the other side (usually the plugin host) will get this value in the implementation of
87//! `<FfiFuture<T> as std::future::Future>::poll`, and explicit propagate the panic,
88//! just like [`std::sync::Mutex`]'s poisoning mechanism.
89//!
90//! ### Panic in `Future::drop` or any waker vtable functions `Waker::*`
91//!
92//! Unfortunately, this is very difficult to handle since drop cleanup and `Waker` functions are
93//! expected to be infallible. If these functions panic, we would just call [`std::process::abort`]
94//! to terminate the whole program.
95//!
96//! ## Variants of `FfiFuture`
97//!
98//! There are a few variants of [`FfiFuture`]. The table below shows their corresponding `std`
99//! type.
100//!
101//! | Type | The corresponding `std` type |
102//! |---------------------------------------------------------------|------------------------------------------------|
103//! | [`FfiFuture<T>`] | `Box<dyn Future<Output = T> + Send + 'static>` |
104//! | [`LocalFfiFuture<T>`] | `Box<dyn Future<Output = T> + 'static>` |
105//! | [`BorrowingFfiFuture<'a, T>`][`BorrowingFfiFuture`] | `Box<dyn Future<Output = T> + Send + 'a>` |
106//! | [`LocalBorrowingFfiFuture<'a, T>`][`LocalBorrowingFfiFuture`] | `Box<dyn Future<Output = T> + 'a>` |
107//!
108//! All of these variants are ABI-compatible to each other, since lifetimes and [`Send`] cannot be
109//! represented by the C ABI. These bounds are only checked in the Rust side. It's your duty to
110//! guarantee that the [`Send`] and lifetime bounds are respected in the foreign code of your
111//! external `fn`s.
112//!
113//! ## Performance and cost
114//!
115//! The conversion between `FfiFuture` and orinary `Future` is not cost-free. Currently
116//! [`FfiFuture::new`] and its alias [`FutureExt::into_ffi`] does one extra allocation.
117//! When `poll`ing an `FfiFuture`, the `Waker` supplied does one extra allocation when `clone`d.
118//!
119//! It's recommended to only wrap you `async` code once right at the FFI boundary, and use ordinary
120//! `Future` everywhere else. It's usually not a good idea to use `FfiFuture` in methods, trait
121//! methods, or generic codes.
122//!
123//! ## [`abi-stable`] support
124//!
125//! If you want to use this crate with [`abi-stable`] interfaces. You can enable the feature flag
126//! `abi_stable` (disabled by default), then the struct `FfiFuture` and friends would derive
127//! `abi_stable::StableAbi`.
128//!
129//! [`abi-stable`]: https://github.com/rodrimati1992/abi_stable_crates/
130#![deny(missing_docs)]
131#![cfg_attr(docsrs, feature(doc_cfg))]
132use std::{
133 convert::{TryFrom, TryInto},
134 fmt,
135 future::Future,
136 marker::PhantomData,
137 mem::{self, ManuallyDrop},
138 pin::Pin,
139 task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
140};
141
142#[cfg(feature = "macros")]
143#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
144pub use macros::async_ffi;
145
146/// The ABI version of [`FfiFuture`] and all variants.
147/// Every non-compatible ABI change will increase this number, as well as the crate major version.
148pub const ABI_VERSION: u32 = 2;
149
150/// The FFI compatible [`std::task::Poll`]
151///
152/// [`std::task::Poll`]: std::task::Poll
153#[repr(C, u8)]
154#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
155pub enum FfiPoll<T> {
156 /// Represents that a value is immediately ready.
157 Ready(T),
158 /// Represents that a value is not ready yet.
159 Pending,
160 /// Represents that the future panicked
161 Panicked,
162}
163
164/// Abort on drop with a message.
165struct DropBomb(&'static str);
166
167impl DropBomb {
168 fn with<T, F: FnOnce() -> T>(message: &'static str, f: F) -> T {
169 let bomb = DropBomb(message);
170 let ret = f();
171 mem::forget(bomb);
172 ret
173 }
174}
175
176impl Drop for DropBomb {
177 fn drop(&mut self) {
178 use std::io::Write;
179 // Use `Stderr::write_all` instead of `eprintln!` to avoid panicking here.
180 let mut stderr = std::io::stderr();
181 let _ = stderr.write_all(b"async-ffi: abort due to panic across the FFI boundary in ");
182 let _ = stderr.write_all(self.0.as_bytes());
183 let _ = stderr.write_all(b"\n");
184 std::process::abort();
185 }
186}
187
188/// The FFI compatible [`std::task::Context`]
189///
190/// [`std::task::Context`]: std::task::Context
191#[repr(C)]
192#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
193pub struct FfiContext<'a> {
194 /// This waker is passed as borrow semantic.
195 /// The external fn must not `drop` or `wake` it.
196 waker: *const FfiWakerBase,
197 /// Lets the compiler know that this references the FfiWaker and should not outlive it
198 _marker: PhantomData<&'a FfiWakerBase>,
199}
200
201impl<'a> FfiContext<'a> {
202 /// SAFETY: Vtable functions of `waker` are unsafe, the caller must ensure they have a
203 /// sane behavior as a Waker. `with_context` relies on this to be safe.
204 unsafe fn new(waker: &'a FfiWaker) -> Self {
205 Self {
206 waker: (waker as *const FfiWaker).cast::<FfiWakerBase>(),
207 _marker: PhantomData,
208 }
209 }
210
211 /// Runs a closure with the [`FfiContext`] as a normal [`std::task::Context`].
212 ///
213 /// [`std::task::Context`]: std::task::Context
214 pub fn with_context<T, F: FnOnce(&mut Context) -> T>(&mut self, closure: F) -> T {
215 // C vtable functions are considered from FFI and they are not expected to unwind, so we don't
216 // need to wrap them with `DropBomb`.
217 static RUST_WAKER_VTABLE: RawWakerVTable = {
218 unsafe fn clone(data: *const ()) -> RawWaker {
219 let waker = data.cast::<FfiWaker>();
220 let cloned = ((*(*waker).base.vtable).clone)(waker.cast());
221 RawWaker::new(cloned.cast(), &RUST_WAKER_VTABLE)
222 }
223 unsafe fn wake(data: *const ()) {
224 let waker = data.cast::<FfiWaker>();
225 ((*(*waker).base.vtable).wake)(waker.cast());
226 }
227 unsafe fn wake_by_ref(data: *const ()) {
228 let waker = data.cast::<FfiWaker>();
229 ((*(*waker).base.vtable).wake_by_ref)(waker.cast());
230 }
231 unsafe fn drop(data: *const ()) {
232 let waker = data.cast::<FfiWaker>();
233 ((*(*waker).base.vtable).drop)(waker.cast());
234 }
235 RawWakerVTable::new(clone, wake, wake_by_ref, drop)
236 };
237
238 // SAFETY: `waker`'s vtable functions must have sane behaviors, this is the contract of
239 // `FfiContext::new`.
240 let waker = unsafe {
241 // The waker reference is borrowed from external context. We must not call drop on it.
242 ManuallyDrop::new(Waker::from_raw(RawWaker::new(
243 self.waker.cast(),
244 &RUST_WAKER_VTABLE,
245 )))
246 };
247 let mut ctx = Context::from_waker(&waker);
248
249 closure(&mut ctx)
250 }
251}
252
253/// Helper trait to provide convenience methods for converting a [`std::task::Context`] to [`FfiContext`]
254///
255/// [`std::task::Context`]: std::task::Context
256pub trait ContextExt {
257 /// Runs a closure with the [`std::task::Context`] as a [`FfiContext`].
258 ///
259 /// [`std::task::Context`]: std::task::Context
260 fn with_ffi_context<T, F: FnOnce(&mut FfiContext) -> T>(&mut self, closure: F) -> T;
261}
262
263impl<'a> ContextExt for Context<'a> {
264 fn with_ffi_context<T, F: FnOnce(&mut FfiContext) -> T>(&mut self, closure: F) -> T {
265 static C_WAKER_VTABLE_OWNED: FfiWakerVTable = {
266 unsafe extern "C" fn clone(data: *const FfiWakerBase) -> *const FfiWakerBase {
267 DropBomb::with("Waker::clone", || {
268 let data = data as *mut FfiWaker;
269 let waker: Waker = (*(*data).waker.owned).clone();
270 Box::into_raw(Box::new(FfiWaker {
271 base: FfiWakerBase {
272 vtable: &C_WAKER_VTABLE_OWNED,
273 },
274 waker: WakerUnion {
275 owned: ManuallyDrop::new(waker),
276 },
277 }))
278 .cast()
279 })
280 }
281 // In this case, we must own `data`. This can only happen when the `data` pointer is returned from `clone`.
282 // Thus the it is `Box<FfiWaker>`.
283 unsafe extern "C" fn wake(data: *const FfiWakerBase) {
284 DropBomb::with("Waker::wake", || {
285 let b = Box::from_raw(data as *mut FfiWaker);
286 ManuallyDrop::into_inner(b.waker.owned).wake();
287 });
288 }
289 unsafe extern "C" fn wake_by_ref(data: *const FfiWakerBase) {
290 DropBomb::with("Waker::wake_by_ref", || {
291 let data = data as *mut FfiWaker;
292 (*data).waker.owned.wake_by_ref();
293 });
294 }
295 // Same as `wake`.
296 unsafe extern "C" fn drop(data: *const FfiWakerBase) {
297 DropBomb::with("Waker::drop", || {
298 let mut b = Box::from_raw(data as *mut FfiWaker);
299 ManuallyDrop::drop(&mut b.waker.owned);
300 mem::drop(b);
301 });
302 }
303 FfiWakerVTable {
304 clone,
305 wake,
306 wake_by_ref,
307 drop,
308 }
309 };
310
311 static C_WAKER_VTABLE_REF: FfiWakerVTable = {
312 unsafe extern "C" fn clone(data: *const FfiWakerBase) -> *const FfiWakerBase {
313 DropBomb::with("Waker::clone", || {
314 let data = data as *mut FfiWaker;
315 let waker: Waker = (*(*data).waker.reference).clone();
316 Box::into_raw(Box::new(FfiWaker {
317 base: FfiWakerBase {
318 vtable: &C_WAKER_VTABLE_OWNED,
319 },
320 waker: WakerUnion {
321 owned: ManuallyDrop::new(waker),
322 },
323 }))
324 .cast()
325 })
326 }
327 unsafe extern "C" fn wake_by_ref(data: *const FfiWakerBase) {
328 DropBomb::with("Waker::wake_by_ref", || {
329 let data = data as *mut FfiWaker;
330 (*(*data).waker.reference).wake_by_ref();
331 });
332 }
333 unsafe extern "C" fn unreachable(_: *const FfiWakerBase) {
334 std::process::abort();
335 }
336 FfiWakerVTable {
337 clone,
338 wake: unreachable,
339 wake_by_ref,
340 drop: unreachable,
341 }
342 };
343
344 let waker = FfiWaker {
345 base: FfiWakerBase {
346 vtable: &C_WAKER_VTABLE_REF,
347 },
348 waker: WakerUnion {
349 reference: self.waker(),
350 },
351 };
352
353 // SAFETY: The behavior of `waker` is sane since we forward them to another valid Waker.
354 // That waker must be safe to use due to the contract of `RawWaker::new`.
355 let mut ctx = unsafe { FfiContext::new(&waker) };
356
357 closure(&mut ctx)
358 }
359}
360
361// Inspired by Gary Guo (github.com/nbdd0121)
362//
363// The base is what can be accessed through FFI, and the regular struct contains
364// internal data (the original waker).
365#[repr(C)]
366#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
367struct FfiWakerBase {
368 vtable: *const FfiWakerVTable,
369}
370#[repr(C)]
371struct FfiWaker {
372 base: FfiWakerBase,
373 waker: WakerUnion,
374}
375
376#[repr(C)]
377union WakerUnion {
378 reference: *const Waker,
379 owned: ManuallyDrop<Waker>,
380 unknown: (),
381}
382
383#[derive(PartialEq, Eq, Hash, Clone, Copy)]
384#[repr(C)]
385#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
386struct FfiWakerVTable {
387 clone: unsafe extern "C" fn(*const FfiWakerBase) -> *const FfiWakerBase,
388 wake: unsafe extern "C" fn(*const FfiWakerBase),
389 wake_by_ref: unsafe extern "C" fn(*const FfiWakerBase),
390 drop: unsafe extern "C" fn(*const FfiWakerBase),
391}
392
393/// The FFI compatible future type with [`Send`] bound.
394///
395/// See [module level documentation](`crate`) for more details.
396#[repr(transparent)]
397#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
398pub struct BorrowingFfiFuture<'a, T>(LocalBorrowingFfiFuture<'a, T>);
399
400/// The FFI compatible future type with [`Send`] bound and `'static` lifetime,
401/// which is needed for most use cases.
402///
403/// See [module level documentation](`crate`) for more details.
404pub type FfiFuture<T> = BorrowingFfiFuture<'static, T>;
405
406/// Helper trait to provide conversion from `Future` to [`FfiFuture`] or [`LocalFfiFuture`].
407///
408/// See [module level documentation](`crate`) for more details.
409pub trait FutureExt: Future + Sized {
410 /// Convert a Rust `Future` implementing [`Send`] into a FFI-compatible [`FfiFuture`].
411 fn into_ffi<'a>(self) -> BorrowingFfiFuture<'a, Self::Output>
412 where
413 Self: Send + 'a,
414 {
415 BorrowingFfiFuture::new(self)
416 }
417
418 /// Convert a Rust `Future` into a FFI-compatible [`LocalFfiFuture`].
419 fn into_local_ffi<'a>(self) -> LocalBorrowingFfiFuture<'a, Self::Output>
420 where
421 Self: 'a,
422 {
423 LocalBorrowingFfiFuture::new(self)
424 }
425}
426
427impl<F> FutureExt for F where F: Future + Sized {}
428
429/// Represents that the poll function panicked.
430#[derive(Debug)]
431pub struct PollPanicked {
432 _private: (),
433}
434
435impl fmt::Display for PollPanicked {
436 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437 f.write_str("FFI poll function panicked")
438 }
439}
440
441impl std::error::Error for PollPanicked {}
442
443impl<T> FfiPoll<T> {
444 /// Converts a [`std::task::Poll`] to the [`FfiPoll`].
445 ///
446 /// [`std::task::Poll`]: std::task::Poll
447 pub fn from_poll(poll: Poll<T>) -> Self {
448 match poll {
449 Poll::Ready(r) => Self::Ready(r),
450 Poll::Pending => Self::Pending,
451 }
452 }
453
454 /// Try to convert a [`FfiPoll`] back to the [`std::task::Poll`].
455 ///
456 /// # Errors
457 /// Returns `Err(PollPanicked)` if the result indicates the poll function panicked.
458 ///
459 /// [`std::task::Poll`]: std::task::Poll
460 pub fn try_into_poll(self) -> Result<Poll<T>, PollPanicked> {
461 match self {
462 Self::Ready(r) => Ok(Poll::Ready(r)),
463 Self::Pending => Ok(Poll::Pending),
464 Self::Panicked => Err(PollPanicked { _private: () }),
465 }
466 }
467}
468
469impl<T> From<Poll<T>> for FfiPoll<T> {
470 fn from(poll: Poll<T>) -> Self {
471 Self::from_poll(poll)
472 }
473}
474
475impl<T> TryFrom<FfiPoll<T>> for Poll<T> {
476 type Error = PollPanicked;
477
478 fn try_from(ffi_poll: FfiPoll<T>) -> Result<Self, PollPanicked> {
479 ffi_poll.try_into_poll()
480 }
481}
482
483impl<'a, T> BorrowingFfiFuture<'a, T> {
484 /// Convert an [`std::future::Future`] implementing [`Send`] into a FFI-compatible [`FfiFuture`].
485 ///
486 /// Usually [`FutureExt::into_ffi`] is preferred and is identical to this method.
487 pub fn new<F: Future<Output = T> + Send + 'a>(fut: F) -> Self {
488 Self(LocalBorrowingFfiFuture::new(fut))
489 }
490}
491
492// SAFETY: This is safe since we allow only `Send` Future in `FfiFuture::new`.
493unsafe impl<T> Send for BorrowingFfiFuture<'_, T> {}
494
495impl<T> Future for BorrowingFfiFuture<'_, T> {
496 type Output = T;
497
498 fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
499 Pin::new(&mut self.0).poll(ctx)
500 }
501}
502
503/// The FFI compatible future type without [`Send`] bound.
504///
505/// Non-[`Send`] `Future`s can only be converted into [`LocalFfiFuture`]. It is not able to be
506/// `spawn`ed in a multi-threaded runtime, but is useful for thread-local futures, single-threaded
507/// runtimes, or single-threaded targets like `wasm32-unknown-unknown`.
508///
509/// See [module level documentation](`crate`) for more details.
510#[repr(C)]
511#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
512pub struct LocalBorrowingFfiFuture<'a, T> {
513 fut_ptr: *mut (),
514 poll_fn: unsafe extern "C" fn(fut_ptr: *mut (), context_ptr: *mut FfiContext) -> FfiPoll<T>,
515 drop_fn: unsafe extern "C" fn(*mut ()),
516 _marker: PhantomData<&'a ()>,
517}
518
519/// The FFI compatible future type without `Send` bound but with `'static` lifetime.
520///
521/// See [module level documentation](`crate`) for more details.
522pub type LocalFfiFuture<T> = LocalBorrowingFfiFuture<'static, T>;
523
524impl<'a, T> LocalBorrowingFfiFuture<'a, T> {
525 /// Convert an [`std::future::Future`] into a FFI-compatible [`LocalFfiFuture`].
526 ///
527 /// Usually [`FutureExt::into_local_ffi`] is preferred and is identical to this method.
528 pub fn new<F: Future<Output = T> + 'a>(fut: F) -> Self {
529 unsafe extern "C" fn poll_fn<F: Future>(
530 fut_ptr: *mut (),
531 context_ptr: *mut FfiContext,
532 ) -> FfiPoll<F::Output> {
533 // The poll fn is likely to panic since it contains most of user logic.
534 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
535 let fut_pin = Pin::new_unchecked(&mut *fut_ptr.cast::<F>());
536 (*context_ptr).with_context(|ctx| F::poll(fut_pin, ctx))
537 })) {
538 Ok(p) => p.into(),
539 Err(payload) => {
540 // Panic payload may panic when dropped, ensure not propagate it.
541 // https://github.com/rust-lang/rust/issues/86027
542 DropBomb::with("drop of panic payload from Future::poll", move || {
543 drop(payload);
544 });
545 FfiPoll::Panicked
546 }
547 }
548 }
549
550 unsafe extern "C" fn drop_fn<T>(ptr: *mut ()) {
551 DropBomb::with("Future::drop", || {
552 drop(Box::from_raw(ptr.cast::<T>()));
553 });
554 }
555
556 let ptr = Box::into_raw(Box::new(fut));
557 Self {
558 fut_ptr: ptr.cast(),
559 poll_fn: poll_fn::<F>,
560 drop_fn: drop_fn::<F>,
561 _marker: PhantomData,
562 }
563 }
564}
565
566impl<T> Drop for LocalBorrowingFfiFuture<'_, T> {
567 fn drop(&mut self) {
568 // SAFETY: This is safe since `drop_fn` is construct from `LocalBorrowingFfiFuture::new`
569 // and is a dropper
570 // `LocalBorrowingFfiFuture::new` and they are just a Box pointer and its corresponding
571 // dropper.
572 unsafe { (self.drop_fn)(self.fut_ptr) };
573 }
574}
575
576impl<T> Future for LocalBorrowingFfiFuture<'_, T> {
577 type Output = T;
578
579 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
580 // SAFETY: This is safe since `poll_fn` is constructed from `LocalBorrowingFfiFuture::new`
581 // and it just forwards to the original safe `Future::poll`.
582 ctx.with_ffi_context(|ctx| unsafe { (self.poll_fn)(self.fut_ptr, ctx) })
583 .try_into()
584 // Propagate panic from FFI.
585 .unwrap_or_else(|_| panic!("FFI future panicked"))
586 }
587}