Expand description
§FFI-compatible Futures
Rust currently doesn’t provide stable ABI nor stable layout of related structs like
dyn Future or Waker.
With this crate, we can wrap async blocks or async functions to make a Future FFI-safe.
FfiFuture provides the same functionality as Box<dyn Future<Output = T> + Send> but
it’s FFI-compatible, aka. repr(C). Any Future<Output = T> + Send + 'static can be converted
into FfiFuture by calling into_ffi on it, after useing the
trait FutureExt.
FfiFuture implements Future<Output = T> + Send. You can await a FfiFuture just like
a normal Future to wait and get the output.
For non-Send or non-'static futures, see the section
Variants of FfiFuture below.
§Examples
Provide some async functions in library: (plugin side)
// Compile with `crate-type = ["cdylib"]`.
use async_ffi::{FfiFuture, FutureExt};
#[no_mangle]
pub extern "C" fn work(arg: u32) -> FfiFuture<u32> {
async move {
let ret = do_some_io(arg).await;
do_some_sleep(42).await;
ret
}
.into_ffi()
}Execute async functions from external library: (host or executor side)
use async_ffi::{FfiFuture, FutureExt};
// #[link(name = "myplugin...")]
extern "C" {
#[no_mangle]
fn work(arg: u32) -> FfiFuture<u32>;
}
async fn run_work(arg: u32) -> u32 {
unsafe { work(arg).await }
}§Proc-macro helpers
If you enable the feature macros (disabled by default), an attribute-like procedural macro
async_ffi
is available at top-level. See its own documentation for details.
With the macro, the example above can be simplified to:
use async_ffi::async_ffi;
#[no_mangle]
#[async_ffi]
pub async extern "C" fn work(arg: u32) -> u32 {
let ret = do_some_io(arg).await;
do_some_sleep(42).await;
ret
}§Panics
You should know that unwinding across an FFI boundary is Undefined Behaviour.
§Panic in Future::poll
Since the body of async fn is translated to Future::poll by the compiler, the poll
method is likely to panic. If this happen, the wrapped FfiFuture will catch unwinding
with std::panic::catch_unwind, returning FfiPoll::Panicked to cross the FFI boundary.
And the other side (usually the plugin host) will get this value in the implementation of
<FfiFuture<T> as std::future::Future>::poll, and explicit propagate the panic,
just like std::sync::Mutex’s poisoning mechanism.
§Panic in Future::drop or any waker vtable functions Waker::*
Unfortunately, this is very difficult to handle since drop cleanup and Waker functions are
expected to be infallible. If these functions panic, we would just call std::process::abort
to terminate the whole program.
§Variants of FfiFuture
There are a few variants of FfiFuture. The table below shows their corresponding std
type.
| Type | The corresponding std type |
|---|---|
FfiFuture<T> | Box<dyn Future<Output = T> + Send + 'static> |
LocalFfiFuture<T> | Box<dyn Future<Output = T> + 'static> |
BorrowingFfiFuture<'a, T> | Box<dyn Future<Output = T> + Send + 'a> |
LocalBorrowingFfiFuture<'a, T> | Box<dyn Future<Output = T> + 'a> |
All of these variants are ABI-compatible to each other, since lifetimes and Send cannot be
represented by the C ABI. These bounds are only checked in the Rust side. It’s your duty to
guarantee that the Send and lifetime bounds are respected in the foreign code of your
external fns.
§Performance and cost
The conversion between FfiFuture and orinary Future is not cost-free. Currently
FfiFuture::new and its alias FutureExt::into_ffi does one extra allocation.
When polling an FfiFuture, the Waker supplied does one extra allocation when cloned.
It’s recommended to only wrap you async code once right at the FFI boundary, and use ordinary
Future everywhere else. It’s usually not a good idea to use FfiFuture in methods, trait
methods, or generic codes.
§abi-stable support
If you want to use this crate with abi-stable interfaces. You can enable the feature flag
abi_stable (disabled by default), then the struct FfiFuture and friends would derive
abi_stable::StableAbi.
Structs§
- Borrowing
FfiFuture - The FFI compatible future type with
Sendbound. - FfiContext
- The FFI compatible
std::task::Context - Local
Borrowing FfiFuture - The FFI compatible future type without
Sendbound. - Poll
Panicked - Represents that the poll function panicked.
Enums§
- FfiPoll
- The FFI compatible
std::task::Poll
Constants§
- ABI_
VERSION - The ABI version of
FfiFutureand all variants. Every non-compatible ABI change will increase this number, as well as the crate major version.
Traits§
- Context
Ext - Helper trait to provide convenience methods for converting a
std::task::ContexttoFfiContext - Future
Ext - Helper trait to provide conversion from
FuturetoFfiFutureorLocalFfiFuture.
Type Aliases§
- FfiFuture
- The FFI compatible future type with
Sendbound and'staticlifetime, which is needed for most use cases. - Local
FfiFuture - The FFI compatible future type without
Sendbound but with'staticlifetime.