8 releases (4 breaking)
0.5.0 | Aug 11, 2023 |
---|---|
0.4.1 | Feb 17, 2023 |
0.4.0 | Feb 5, 2022 |
0.3.1 | Nov 14, 2021 |
0.1.0 | Mar 30, 2021 |
#343 in FFI
21,884 downloads per month
Used in 11 crates
(7 directly)
26KB
328 lines
async-ffi: FFI-compatible Future
s
Convert your Rust Future
s into a FFI-compatible struct without relying unstable Rust ABI and struct layout.
Easily provide async functions in dynamic library maybe compiled with different Rust than the invoker.
See documentation for more details.
See link_tests
directory for cross-linking examples.
License
MIT Licensed.
lib.rs
:
FFI-compatible Future
s
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 use
ing 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
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 fn
s.
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 poll
ing an FfiFuture
, the Waker
supplied does one extra allocation when clone
d.
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
.
Dependencies
~0–5.5MB
~20K SLoC