12 个版本
0.2.10 | 2024年2月22日 |
---|---|
0.2.7 | 2023年2月1日 |
0.2.6 | 2021年5月28日 |
0.2.4 | 2021年3月28日 |
0.1.1 | 2020年1月15日 |
#28 in 过程宏
每月下载量360,524
在 170 个crates中使用 (64 个直接使用)
41KB
421 行
maybe-async
为什么要在同步和异步代码中重复写相似的代码呢?
在crate中实现API的同步和异步版本时,这两个版本的大部分API几乎相同,只是有一些异步/等待关键字。
maybe-async
通过 过程宏 帮助统一异步和同步实现。
- 使用常规的
async
、await
编写异步代码,当需要阻塞代码时,让maybe_async
处理那些async
和await
。 - 通过在
Cargo.toml
中切换is_sync
功能门来在同步和异步之间切换。 - 使用
must_be_async
和must_be_sync
保持代码在指定版本 - 使用
async_impl
和sync_impl
仅在指定版本上编译代码块 - 还提供了一个方便的宏来统一单元测试代码。
这些过程宏可以应用于以下代码
- trait项声明
- trait实现
- 函数定义
- 结构定义
建议:在您的crate中启用 resolver ver2,这是Rust 1.51中引入的。如果不启用,具有冲突版本(一个异步和一个阻塞)的依赖项的crate可能会失败编译。
动机
async/await语言特性改变了Rust的异步世界。与map/and_then风格相比,现在的异步代码真正类似于同步版本的代码。
在许多包中,异步和同步版本的包共享相同的API,但所有异步代码都必须等待的细微差异阻止了异步和同步代码的统一。换句话说,我们被迫分别编写异步和同步实现。
宏的详细说明
maybe-async
提供了4组属性宏:maybe_async
、sync_impl/
async_impl
、must_be_sync/
must_be_async
和 test
。
要使用 maybe-async
,我们必须知道哪些代码块仅用于阻塞实现,哪些用于异步。这两种实现应共享相同的函数签名,除了异步/等待关键字外,并使用 sync_impl
和 async_impl
标记这些实现。
在共享相同API的代码上使用 maybe_async
宏(除了异步/等待关键字外),并在 Cargo.toml
中使用特性门 is_sync
在异步和阻塞代码之间切换。
-
maybe_async
通过特性门
is_sync
提供统一的特性门,以便在需要时通过特性门提供同步和异步转换,具有 异步优先 策略。想保留异步代码?在默认特性中将
maybe_async
添加到依赖项中,这意味着maybe_async
与must_be_async
相同。[dependencies] maybe_async = "0.2"
想将异步代码转换为同步?将
maybe_async
添加到依赖项中并带有is_sync
特性门。这样,maybe_async
与must_be_sync
相同。[dependencies] maybe_async = { version = "0.2", features = ["is_sync"] }
maybe_async
属性使用有三种用法变体-
#[maybe_async]
或#[maybe_async(Send)]
在此模式下,将
#[async_trait::async_trait]
添加到特性和特性实现中,以支持特性中的异步 fn。 -
#[maybe_async(?Send)]
并非所有异步特性都需要
dyn Future + Send
的 futures。在此模式下,将#[async_trait::async_trait(?Send)]
添加到特性和特性实现中,以避免在异步特性方法上放置 "Send" 和 "Sync" 约束。 -
#[maybe_async(AFIT)]
AFIT 是 async function in trait 的缩写,从 rust 1.74 开始稳定。
出于兼容性原因,特性和特性实现中的
async fn
通过冗长的AFIT
标志支持。这将成为下一个主要版本的默认模式。 -
-
must_be_async
保持异步.
must_be_async
属性使用有三种用法变体#[must_be_async]
或#[must_be_async(Send)]
#[must_be_async(?Send)]
#[must_be_async(AFIT)]
-
must_be_sync
转换为同步代码。通过删除所有
async move
、async
和await
关键字将异步代码转换为同步代码。 -
sync_impl
同步实现应该在阻塞实现上编译,并且当我们想要异步版本时,必须简单地消失。
尽管大多数API几乎相同,但异步版本和同步版本肯定会在某个点上出现很大的差异。例如,MongoDB客户端可能使用相同的API进行异步和同步版本,但实际上发送请求的代码却大不相同。
在这里,我们可以使用
sync_impl
来标记同步实现,并且当我们需要异步版本时,同步实现应该消失。 -
async_impl
异步实现应该在异步实现上编译,并且当我们想要同步版本时,必须简单地消失。
async_impl
属性的用法有三种变体#[async_impl]
或#[async_impl(Send)]
#[async_impl(?Send)]
#[async_impl(AFIT)]
-
test
方便的宏,用于统一异步和同步 单元和端到端测试 代码。
您可以使用给定的测试宏指定编译为同步测试代码的条件以及编译为异步测试代码的条件,例如
tokio::test
、async_std::test
等。当仅指定同步条件时,测试代码仅在满足同步条件时编译。# #[maybe_async::maybe_async] # async fn async_fn() -> bool { # true # } ##[maybe_async::test( feature="is_sync", async( all(not(feature="is_sync"), feature="async_std"), async_std::test ), async( all(not(feature="is_sync"), feature="tokio"), tokio::test ) )] async fn test_async_fn() { let res = async_fn().await; assert_eq!(res, true); }
钩子下面是什么
maybe-async
使用 is_sync
功能门在不同方式下编译您的代码。它在 maybe_async
宏下移除您的代码中的所有 await
和 async
关键字,并根据条件编译 async_impl
和 sync_impl
下的代码。
以下是一个详细示例,说明当 is_sync
功能门设置或不设置时,发生了什么。
#[maybe_async::maybe_async(AFIT)]
trait A {
async fn async_fn_name() -> Result<(), ()> {
Ok(())
}
fn sync_fn_name() -> Result<(), ()> {
Ok(())
}
}
struct Foo;
#[maybe_async::maybe_async(AFIT)]
impl A for Foo {
async fn async_fn_name() -> Result<(), ()> {
Ok(())
}
fn sync_fn_name() -> Result<(), ()> {
Ok(())
}
}
#[maybe_async::maybe_async]
async fn maybe_async_fn() -> Result<(), ()> {
let a = Foo::async_fn_name().await?;
let b = Foo::sync_fn_name()?;
Ok(())
}
当 maybe-async
功能门 is_sync
未设置时,生成的代码是异步代码
// Compiled code when `is_sync` is toggled off.
trait A {
async fn maybe_async_fn_name() -> Result<(), ()> {
Ok(())
}
fn sync_fn_name() -> Result<(), ()> {
Ok(())
}
}
struct Foo;
impl A for Foo {
async fn maybe_async_fn_name() -> Result<(), ()> {
Ok(())
}
fn sync_fn_name() -> Result<(), ()> {
Ok(())
}
}
async fn maybe_async_fn() -> Result<(), ()> {
let a = Foo::maybe_async_fn_name().await?;
let b = Foo::sync_fn_name()?;
Ok(())
}
当 maybe-async
功能门 is_sync
设置时,所有异步关键字都被忽略,并生成同步版本的代码
// Compiled code when `is_sync` is toggled on.
trait A {
fn maybe_async_fn_name() -> Result<(), ()> {
Ok(())
}
fn sync_fn_name() -> Result<(), ()> {
Ok(())
}
}
struct Foo;
impl A for Foo {
fn maybe_async_fn_name() -> Result<(), ()> {
Ok(())
}
fn sync_fn_name() -> Result<(), ()> {
Ok(())
}
}
fn maybe_async_fn() -> Result<(), ()> {
let a = Foo::maybe_async_fn_name()?;
let b = Foo::sync_fn_name()?;
Ok(())
}
示例
服务客户端的rust
当为任何服务实现rust客户端时,例如awz3。异步和同步版本的API的高层几乎相同,例如创建或删除存储桶、检索对象等。
示例 service_client
是一个概念证明,说明 maybe_async
可以使我们摆脱为同步和异步编写几乎相同的代码。当我们向依赖项添加 maybe-async
时,可以通过 is_sync
功能门在同步AWZ3客户端和异步客户端之间切换。
许可协议
MIT
依赖关系
~235–670KB
~16K SLoC