#async #cpp #async-await #ffi #async-io

sys cxx-async

实现C++协程和异步Rust之间的简单互操作性

2个版本

0.1.1 2022年9月21日
0.1.0 2022年9月3日

#842异步

MIT/Apache

54KB
839

cxx-async

概述

cxx-async 是一个Rust包,它扩展了 cxx 库,以便通过 async/await 使用 co_await 在异步Rust代码和 C++20 协程 之间提供互操作性。如果你的C++代码是异步的,cxx-async 可以提供一个更方便的,也许更高效的,相对于回调的替代方案。你可以自由地在C++协程和Rust futures和/或 streams 之间转换,并从另一个中等待。

重要的是要强调 cxx-async 不是什么:它不是Tokio或其他Rust I/O库的C++绑定。它也不是Rust绑定到 boost::asio 或类似库。这样的绑定如果需要可以在 cxx-async 之上进行分层,但这个包并不提供它们。请注意,即使在理论中,这也是一个棘手的问题,因为Rust异步I/O代码通常紧密耦合到单个库(如Tokio),与C++异步I/O代码紧密耦合到像 boost::asio 这样的库一样。如果你在编写服务器代码,你仍然可以使用 cxx-async,但你需要确保Rust和C++两边运行独立的I/O执行器。

cxx-async 旨在与流行的C++协程支持库兼容。目前,轻量级的 cppcoro 和更全面的 Folly 都得到了支持。欢迎提交拉取请求以支持其他库。

快速教程

要使用 cxx-async,首先将 cxx 添加到你的项目中。然后,将以下内容添加到你的 Cargo.toml

[dependencies]
cxx-async = "0.1"

现在,在 #[cxx::bridge] 模块中,声明一个future类型和一些方法,如下所示

#[cxx::bridge]
mod ffi {
    // Declare type aliases for each of the future types you wish to use here. Then declare
    // async C++ methods that you wish Rust to call. Make sure they return one of the future
    // types you declared.
    unsafe extern "C++" {
        type RustFutureString = crate::RustFutureString;

        fn hello_from_cpp() -> RustFutureString;
    }

    // Async Rust methods that you wish C++ to call go here. Again, make sure they return one of the
    // boxed future types you declared above.
    extern "Rust" {
        fn hello_from_rust() -> Box<RustFutureString>;
    }
}

#[cxx::bridge] 块之后,使用 #[cxx_async::bridge] 属性定义未来类型

// The inner type is the Rust type that this future yields.
#[cxx_async::bridge]
unsafe impl Future for RustFutureString {
    type Output = String;
}

现在,在你的 C++ 文件中,确保包含正确的头文件

#include "rust/cxx.h"
#include "rust/cxx_async.h"
#include "rust/cxx_async_cppcoro.h"  // Or cxx_async_folly.h, as appropriate.

并添加对 CXXASYNC_DEFINE_FUTURE 宏的调用,以定义未来 C++ 方面

// The first argument is the C++ type that the future yields, and the second argument is the
// fully-qualified name of the future, with `::` namespace separators replaced with commas. (For
// instance, if your future is named `mycompany::myproject::RustFutureString`, you might write
// `CXXASYNC_DEFINE_FUTURE(rust::String, mycompany, myproject, RustFutureString);`. The first
// argument is the C++ type that `cxx` maps your Rust type to: in this case, `String` maps to
// `rust::String`, so we supply `rust::String` here.
//
// This macro must be invoked at the top level, not in a namespace.
CXXASYNC_DEFINE_FUTURE(rust::String, RustFutureString);

一切准备就绪!现在你可以定义 Rust 可以调用的异步 C++ 代码

RustFutureString hello_from_cpp() {
    co_return std::string("Hello world!");
}

在 Rust 方面

async fn call_cpp() -> String {
    // This returns a Result (with the error variant populated if C++ threw an exception), so you
    // need to unwrap it:
    ffi::hello_from_cpp().await.unwrap()
}

同样,定义一些 C++ 可以调用的异步 Rust 代码

use cxx_async::CxxAsyncResult;
fn hello_from_rust() -> RustFutureString {
    // You can instead use `fallible` if your async block returns a Result.
    RustFutureString::infallible(async { "Hello world!".to_owned() })
}

在 C++ 方面

cppcoro::task<rust::String> call_rust() {
    co_return hello_from_rust();
}

这就对了!你现在可以在任一侧自由地等待未来。可以遵循类似的程序将 C++ 协程(使用 co_yield 产生值)包装在 Rust 流中。

安装说明

您需要一个实现了协程 TS 的 C++ 编译器,这通常与对 C++20 的支持相一致。一些实现协程 TS 的 C++ 编译器(例如 Apple clang 13.0.0)在编译 Folly 时会崩溃。还建议使用 libc++ 而不是 libstdc++,因为前者对协程有更完整的支持。

使用 cxx-async 与 Folly 需要Folly具有协程支持。这通常意味着您需要使用 -DCXX_STD=20 构建Folly。许多 Folly 的分发(例如 Homebrew 中的分发)没有启用协程支持;这种错误的一个常见症状是链接错误,提到了缺少符号 folly::resumeCoroutineWithNewAsyncStackRoot

行为准则

cxx-async 遵循 Rust 本身的同一行为准则。可以向包作者提交报告。

许可协议

根据您的选择,受 Apache License 2.0 或 MIT 许可协议的许可。

除非您明确说明,否则根据 Apache-2.0 许可协议定义的,您有意提交的任何贡献,都将按照上述方式双许可,不附加任何其他条款或条件。

依赖关系

~1.3–3MB
~49K SLoC