• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..--

examples/25-Apr-2025-7848

src/25-Apr-2025-859410

tests/25-Apr-2025-609461

.cargo-checksum.jsonD25-Apr-20252.5 KiB11

Android.bpD25-Apr-2025811 3228

CHANGELOG.mdD25-Apr-20253.1 KiB7334

Cargo.lockD25-Apr-202527.5 KiB1,073956

Cargo.tomlD25-Apr-20251.4 KiB7159

LICENSED25-Apr-20251 KiB2016

METADATAD25-Apr-2025411 1817

MODULE_LICENSE_MITD25-Apr-20250

README.mdD25-Apr-20258.8 KiB296226

README.tplD25-Apr-202524 42

cargo_embargo.jsonD25-Apr-202527 43

README.md

1# maybe-async
2
3**Why bother writing similar code twice for blocking and async code?**
4
5[![Build Status](https://github.com/fMeow/maybe-async-rs/workflows/CI%20%28Linux%29/badge.svg?branch=main)](https://github.com/fMeow/maybe-async-rs/actions)
6[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
7[![Latest Version](https://img.shields.io/crates/v/maybe-async.svg)](https://crates.io/crates/maybe-async)
8[![maybe-async](https://docs.rs/maybe-async/badge.svg)](https://docs.rs/maybe-async)
9
10When implementing both sync and async versions of API in a crate, most API
11of the two version are almost the same except for some async/await keyword.
12
13`maybe-async` help unifying async and sync implementation by **procedural
14macro**.
15- Write async code with normal `async`, `await`, and let `maybe_async`
16  handles
17those `async` and `await` when you need a blocking code.
18- Switch between sync and async by toggling `is_sync` feature gate in
19  `Cargo.toml`.
20- use `must_be_async` and `must_be_sync` to keep code in specified version
21- use `async_impl` and `sync_impl` to only compile code block on specified
22  version
23- A handy macro to unify unit test code is also provided.
24
25These procedural macros can be applied to the following codes:
26- trait item declaration
27- trait implementation
28- function definition
29- struct definition
30
31**RECOMMENDATION**: Enable **resolver ver2** in your crate, which is
32introduced in Rust 1.51. If not, two crates in dependency with conflict
33version (one async and another blocking) can fail compilation.
34
35
36### Motivation
37
38The async/await language feature alters the async world of rust.
39Comparing with the map/and_then style, now the async code really resembles
40sync version code.
41
42In many crates, the async and sync version of crates shares the same API,
43but the minor difference that all async code must be awaited prevent the
44unification of async and sync code. In other words, we are forced to write
45an async and a sync implementation respectively.
46
47### Macros in Detail
48
49`maybe-async` offers 4 set of attribute macros: `maybe_async`,
50`sync_impl`/`async_impl`, `must_be_sync`/`must_be_async`,  and `test`.
51
52To use `maybe-async`, we must know which block of codes is only used on
53blocking implementation, and which on async. These two implementation should
54share the same function signatures except for async/await keywords, and use
55`sync_impl` and `async_impl` to mark these implementation.
56
57Use `maybe_async` macro on codes that share the same API on both async and
58blocking code except for async/await keywords. And use feature gate
59`is_sync` in `Cargo.toml` to toggle between async and blocking code.
60
61- `maybe_async`
62
63    Offers a unified feature gate to provide sync and async conversion on
64    demand by feature gate `is_sync`, with **async first** policy.
65
66    Want to keep async code? add `maybe_async` in dependencies with default
67    features, which means `maybe_async` is the same as `must_be_async`:
68
69    ```toml
70    [dependencies]
71    maybe_async = "0.2"
72    ```
73
74    Want to convert async code to sync? Add `maybe_async` to dependencies with
75    an `is_sync` feature gate. In this way, `maybe_async` is the same as
76    `must_be_sync`:
77
78    ```toml
79    [dependencies]
80    maybe_async = { version = "0.2", features = ["is_sync"] }
81    ```
82
83    There are three usage variants for `maybe_async` attribute usage:
84    - `#[maybe_async]` or `#[maybe_async(Send)]`
85
86       In this mode, `#[async_trait::async_trait]` is added to trait declarations and trait implementations
87       to support async fn in traits.
88
89    - `#[maybe_async(?Send)]`
90
91       Not all async traits need futures that are `dyn Future + Send`.
92       In this mode, `#[async_trait::async_trait(?Send)]` is added to trait declarations and trait implementations,
93       to avoid having "Send" and "Sync" bounds placed on the async trait
94       methods.
95
96    - `#[maybe_async(AFIT)]`
97
98       AFIT is acronym for **a**sync **f**unction **i**n **t**rait, stabilized from rust 1.74
99
100    For compatibility reasons, the `async fn` in traits is supported via a verbose `AFIT` flag. This will become
101    the default mode for the next major release.
102
103- `must_be_async`
104
105    **Keep async**.
106
107    There are three usage variants for `must_be_async` attribute usage:
108    - `#[must_be_async]` or `#[must_be_async(Send)]`
109    - `#[must_be_async(?Send)]`
110    - `#[must_be_async(AFIT)]`
111
112- `must_be_sync`
113
114    **Convert to sync code**. Convert the async code into sync code by
115    removing all `async move`, `async` and `await` keyword
116
117
118- `sync_impl`
119
120    A sync implementation should compile on blocking implementation and
121    must simply disappear when we want async version.
122
123    Although most of the API are almost the same, there definitely come to a
124    point when the async and sync version should differ greatly. For
125    example, a MongoDB client may use the same API for async and sync
126    version, but the code to actually send reqeust are quite different.
127
128    Here, we can use `sync_impl` to mark a synchronous implementation, and a
129    sync implementation should disappear when we want async version.
130
131- `async_impl`
132
133    An async implementation should on compile on async implementation and
134    must simply disappear when we want sync version.
135
136    There are three usage variants for `async_impl` attribute usage:
137    - `#[async_impl]` or `#[async_impl(Send)]`
138    - `#[async_impl(?Send)]`
139    - `#[async_impl(AFIT)]`
140
141- `test`
142
143    Handy macro to unify async and sync **unit and e2e test** code.
144
145    You can specify the condition to compile to sync test code
146    and also the conditions to compile to async test code with given test
147    macro, e.x. `tokio::test`, `async_std::test`, etc. When only sync
148    condition is specified,the test code only compiles when sync condition
149    is met.
150
151    ```rust
152    # #[maybe_async::maybe_async]
153    # async fn async_fn() -> bool {
154    #    true
155    # }
156
157    ##[maybe_async::test(
158        feature="is_sync",
159        async(
160            all(not(feature="is_sync"), feature="async_std"),
161            async_std::test
162        ),
163        async(
164            all(not(feature="is_sync"), feature="tokio"),
165            tokio::test
166        )
167    )]
168    async fn test_async_fn() {
169        let res = async_fn().await;
170        assert_eq!(res, true);
171    }
172    ```
173
174### What's Under the Hook
175
176`maybe-async` compiles your code in different way with the `is_sync` feature
177gate. It removes all `await` and `async` keywords in your code under
178`maybe_async` macro and conditionally compiles codes under `async_impl` and
179`sync_impl`.
180
181Here is a detailed example on what's going on whe the `is_sync` feature
182gate set or not.
183
184```rust
185#[maybe_async::maybe_async(AFIT)]
186trait A {
187    async fn async_fn_name() -> Result<(), ()> {
188        Ok(())
189    }
190    fn sync_fn_name() -> Result<(), ()> {
191        Ok(())
192    }
193}
194
195struct Foo;
196
197#[maybe_async::maybe_async(AFIT)]
198impl A for Foo {
199    async fn async_fn_name() -> Result<(), ()> {
200        Ok(())
201    }
202    fn sync_fn_name() -> Result<(), ()> {
203        Ok(())
204    }
205}
206
207#[maybe_async::maybe_async]
208async fn maybe_async_fn() -> Result<(), ()> {
209    let a = Foo::async_fn_name().await?;
210
211    let b = Foo::sync_fn_name()?;
212    Ok(())
213}
214```
215
216When `maybe-async` feature gate `is_sync` is **NOT** set, the generated code
217is async code:
218
219```rust
220// Compiled code when `is_sync` is toggled off.
221trait A {
222    async fn maybe_async_fn_name() -> Result<(), ()> {
223        Ok(())
224    }
225    fn sync_fn_name() -> Result<(), ()> {
226        Ok(())
227    }
228}
229
230struct Foo;
231
232impl A for Foo {
233    async fn maybe_async_fn_name() -> Result<(), ()> {
234        Ok(())
235    }
236    fn sync_fn_name() -> Result<(), ()> {
237        Ok(())
238    }
239}
240
241async fn maybe_async_fn() -> Result<(), ()> {
242    let a = Foo::maybe_async_fn_name().await?;
243    let b = Foo::sync_fn_name()?;
244    Ok(())
245}
246```
247
248When `maybe-async` feature gate `is_sync` is set, all async keyword is
249ignored and yields a sync version code:
250
251```rust
252// Compiled code when `is_sync` is toggled on.
253trait A {
254    fn maybe_async_fn_name() -> Result<(), ()> {
255        Ok(())
256    }
257    fn sync_fn_name() -> Result<(), ()> {
258        Ok(())
259    }
260}
261
262struct Foo;
263
264impl A for Foo {
265    fn maybe_async_fn_name() -> Result<(), ()> {
266        Ok(())
267    }
268    fn sync_fn_name() -> Result<(), ()> {
269        Ok(())
270    }
271}
272
273fn maybe_async_fn() -> Result<(), ()> {
274    let a = Foo::maybe_async_fn_name()?;
275    let b = Foo::sync_fn_name()?;
276    Ok(())
277}
278```
279
280### Examples
281
282#### rust client for services
283
284When implementing rust client for any services, like awz3. The higher level
285API of async and sync version is almost the same, such as creating or
286deleting a bucket, retrieving an object, etc.
287
288The example `service_client` is a proof of concept that `maybe_async` can
289actually free us from writing almost the same code for sync and async. We
290can toggle between a sync AWZ3 client and async one by `is_sync` feature
291gate when we add `maybe-async` to dependency.
292
293
294## License
295MIT
296

README.tpl

1# {{crate}}
2
3{{readme}}
4