8个版本 (4个重大更改)
0.5.0 | 2023年8月11日 |
---|---|
0.4.1 | 2023年2月17日 |
0.4.0 | 2022年2月5日 |
0.3.1 | 2021年11月14日 |
0.1.0 | 2021年3月30日 |
#282 在 异步 类别中
14,531 每月下载量
在 8 个 crate 中使用 (4个直接使用)
26KB
328 行
async-ffi: FFI兼容的 Future
将您的Rust Future
转换为FFI兼容的结构体,而无需依赖不稳定的Rust ABI和结构体布局。轻松地在可能使用不同Rust编译的动态库中提供异步函数。
查看文档以获取更多详细信息。
查看link_tests
目录中的交叉链接示例。
许可证
MIT许可。
lib.rs
:
FFI兼容的 Future
Rust目前不提供稳定的ABI也不提供相关结构体(如dyn Future
或 Waker
)的稳定布局。使用这个crate,我们可以将异步块或异步函数包装起来,使其Future
FFI安全。
FfiFuture
提供与 Box<dyn Future<Output = T> + Send>
相同的功能,但它与 FFI 兼容,即 repr(C)
。任何 Future<Output = T> + Send + 'static
都可以通过调用 into_ffi
并使用特 FutureExt
转换为 FfiFuture
。
FfiFuture
实现 Future<Output = T> + Send
。你可以像调用正常的 Future
一样等待和获取 FfiFuture
的输出。
对于非 Send
或非 'static
的 futures,请参阅下文的 `FfiFuture` 变体 部分。
示例
在库中提供一些异步函数:(插件端)
// 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()
}
执行外部库中的异步函数:(宿主或执行器端)
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 辅助工具
如果您启用功能 macros
(默认禁用),则可以在顶层使用类似属性的程序宏。有关详细信息,请参阅其自己的文档。
使用宏,上面的示例可以简化为:
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
}
恐慌
您应该知道,跨 FFI 边界展开是未定义行为。
Future::poll
中的恐慌
由于编译器将异步函数 async fn
的主体翻译为 Future::poll
,因此 poll
方法可能会引发恐慌。如果发生这种情况,包装的 FfiFuture
将使用 std::panic::catch_unwind
捕获展开,返回 FfiPoll::Panicked
以跨过 FFI 边界。而另一边(通常是插件宿主)将在 <FfiFuture<T> as std::future::Future>::poll
的实现中获得此值,并显式传播恐慌,就像 std::sync::Mutex
的中毒机制。
在 Future::drop
或任何唤醒器 vtable 函数 Waker::*
遗憾的是,这非常难以处理,因为预期 drop 清理和 Waker
函数是不可错误的。如果这些函数引发恐慌,我们将调用 std::process::abort
来终止整个程序。
FfiFuture
的变体
有几种 FfiFuture
的变体。下表显示了它们对应的 std
类型。
类型 | 对应的 std 类型 |
---|---|
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> |
所有这些变体彼此之间都兼容 ABI,因为生命周期和 Send
无法由 C ABI 表示。这些界限仅在 Rust 端进行检查。您有责任确保在您的外部 fn
的外部代码中尊重 Send
和生命周期界限。
性能和成本
FfiFuture
和普通 Future
之间的转换并非没有代价。目前,FfiFuture::new
和其别名 FutureExt::into_ffi
做了一个额外的分配。当轮询 FfiFuture
时,提供的 Waker
在 clone
时也做了额外的分配。
建议只在FFI边界处对您的 async
代码进行一次包装,并在其他地方使用普通的 Future
。通常在方法、特例方法或泛型代码中使用 FfiFuture
不是一个好主意。
abi-stable
支持
如果您想使用此crate与 abi-stable
接口。您可以通过启用功能标志 abi_stable
(默认禁用)来实现,然后结构体 FfiFuture
和朋友将派生 abi_stable::StableAbi
。
依赖关系
~0-6MB
~17K SLoC