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异步 类别中

Download history 2543/week @ 2024-03-14 3273/week @ 2024-03-21 2054/week @ 2024-03-28 2279/week @ 2024-04-04 2812/week @ 2024-04-11 3525/week @ 2024-04-18 4457/week @ 2024-04-25 3649/week @ 2024-05-02 2536/week @ 2024-05-09 5776/week @ 2024-05-16 7736/week @ 2024-05-23 4773/week @ 2024-05-30 3527/week @ 2024-06-06 3998/week @ 2024-06-13 3750/week @ 2024-06-20 2608/week @ 2024-06-27

14,531 每月下载量
8 crate 中使用 (4个直接使用)

MIT 许可证

26KB
328

async-ffi: FFI兼容的 Future

crates.io docs.rs CI

将您的Rust Future 转换为FFI兼容的结构体,而无需依赖不稳定的Rust ABI和结构体布局。轻松地在可能使用不同Rust编译的动态库中提供异步函数。

查看文档以获取更多详细信息。

查看link_tests 目录中的交叉链接示例。

许可证

MIT许可。


lib.rs:

FFI兼容的 Future

Rust目前不提供稳定的ABI也不提供相关结构体(如dyn FutureWaker)的稳定布局。使用这个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 时,提供的 Wakerclone 时也做了额外的分配。

建议只在FFI边界处对您的 async 代码进行一次包装,并在其他地方使用普通的 Future。通常在方法、特例方法或泛型代码中使用 FfiFuture 不是一个好主意。

abi-stable 支持

如果您想使用此crate与 abi-stable 接口。您可以通过启用功能标志 abi_stable(默认禁用)来实现,然后结构体 FfiFuture 和朋友将派生 abi_stable::StableAbi

依赖关系

~0-6MB
~17K SLoC