#async-api #async #proc-macro #async-version #macro #async-write #maybe

maybe-async

一个统一同步和异步实现的程序宏

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 过程宏

Download history 82738/week @ 2024-04-27 79517/week @ 2024-05-04 85240/week @ 2024-05-11 77664/week @ 2024-05-18 72875/week @ 2024-05-25 81644/week @ 2024-06-01 83593/week @ 2024-06-08 85011/week @ 2024-06-15 75644/week @ 2024-06-22 73932/week @ 2024-06-29 77186/week @ 2024-07-06 85890/week @ 2024-07-13 87255/week @ 2024-07-20 94290/week @ 2024-07-27 91298/week @ 2024-08-03 74587/week @ 2024-08-10

每月下载量360,524
170 个crates中使用 (64 个直接使用)

MIT 许可证

41KB
421

maybe-async

为什么要在同步和异步代码中重复写相似的代码呢?

Build Status MIT licensed Latest Version maybe-async

在crate中实现API的同步和异步版本时,这两个版本的大部分API几乎相同,只是有一些异步/等待关键字。

maybe-async 通过 过程宏 帮助统一异步和同步实现。

  • 使用常规的 asyncawait 编写异步代码,当需要阻塞代码时,让 maybe_async 处理那些 asyncawait
  • 通过在 Cargo.toml 中切换 is_sync 功能门来在同步和异步之间切换。
  • 使用 must_be_asyncmust_be_sync 保持代码在指定版本
  • 使用 async_implsync_impl 仅在指定版本上编译代码块
  • 还提供了一个方便的宏来统一单元测试代码。

这些过程宏可以应用于以下代码

  • trait项声明
  • trait实现
  • 函数定义
  • 结构定义

建议:在您的crate中启用 resolver ver2,这是Rust 1.51中引入的。如果不启用,具有冲突版本(一个异步和一个阻塞)的依赖项的crate可能会失败编译。

动机

async/await语言特性改变了Rust的异步世界。与map/and_then风格相比,现在的异步代码真正类似于同步版本的代码。

在许多包中,异步和同步版本的包共享相同的API,但所有异步代码都必须等待的细微差异阻止了异步和同步代码的统一。换句话说,我们被迫分别编写异步和同步实现。

宏的详细说明

maybe-async 提供了4组属性宏:maybe_asyncsync_impl/async_implmust_be_sync/must_be_asynctest

要使用 maybe-async,我们必须知道哪些代码块仅用于阻塞实现,哪些用于异步。这两种实现应共享相同的函数签名,除了异步/等待关键字外,并使用 sync_implasync_impl 标记这些实现。

在共享相同API的代码上使用 maybe_async 宏(除了异步/等待关键字外),并在 Cargo.toml 中使用特性门 is_sync 在异步和阻塞代码之间切换。

  • maybe_async

    通过特性门 is_sync 提供统一的特性门,以便在需要时通过特性门提供同步和异步转换,具有 异步优先 策略。

    想保留异步代码?在默认特性中将 maybe_async 添加到依赖项中,这意味着 maybe_asyncmust_be_async 相同。

    [dependencies]
    maybe_async = "0.2"
    

    想将异步代码转换为同步?将 maybe_async 添加到依赖项中并带有 is_sync 特性门。这样,maybe_asyncmust_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 moveasyncawait 关键字将异步代码转换为同步代码。

  • sync_impl

    同步实现应该在阻塞实现上编译,并且当我们想要异步版本时,必须简单地消失。

    尽管大多数API几乎相同,但异步版本和同步版本肯定会在某个点上出现很大的差异。例如,MongoDB客户端可能使用相同的API进行异步和同步版本,但实际上发送请求的代码却大不相同。

    在这里,我们可以使用 sync_impl 来标记同步实现,并且当我们需要异步版本时,同步实现应该消失。

  • async_impl

    异步实现应该在异步实现上编译,并且当我们想要同步版本时,必须简单地消失。

    async_impl 属性的用法有三种变体

    • #[async_impl]#[async_impl(Send)]
    • #[async_impl(?Send)]
    • #[async_impl(AFIT)]
  • test

    方便的宏,用于统一异步和同步 单元和端到端测试 代码。

    您可以使用给定的测试宏指定编译为同步测试代码的条件以及编译为异步测试代码的条件,例如 tokio::testasync_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 宏下移除您的代码中的所有 awaitasync 关键字,并根据条件编译 async_implsync_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