Skip to content

Commit 282eb76

Browse files
committed
feat: support async fn in traits
1 parent ab7f3fc commit 282eb76

File tree

6 files changed

+200
-108
lines changed

6 files changed

+200
-108
lines changed

Diff for: Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ quote = "1.0"
2020

2121
[lib]
2222
proc-macro = true
23+
path = "src/lib.rs"
2324

2425
[badges.maintenance]
2526
status = "actively-developed"

Diff for: README.md

+63-35
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# Maybe-Async Procedure Macro
1+
![Maintenance](https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg)
2+
3+
# maybe-async
24

35
**Why bother writing similar code twice for blocking and async code?**
46

@@ -24,16 +26,16 @@ those `async` and `await` when you need a blocking code.
2426

2527
These procedural macros can be applied to the following codes:
2628
- trait item declaration
27-
- trait implmentation
29+
- trait implementation
2830
- function definition
2931
- struct definition
3032

3133
**RECOMMENDATION**: Enable **resolver ver2** in your crate, which is
3234
introduced in Rust 1.51. If not, two crates in dependency with conflict
33-
version (one async and another blocking) can fail complilation.
35+
version (one async and another blocking) can fail compilation.
3436

3537

36-
## Motivation
38+
### Motivation
3739

3840
The async/await language feature alters the async world of rust.
3941
Comparing with the map/and_then style, now the async code really resembles
@@ -42,9 +44,9 @@ sync version code.
4244
In many crates, the async and sync version of crates shares the same API,
4345
but the minor difference that all async code must be awaited prevent the
4446
unification of async and sync code. In other words, we are forced to write
45-
an async and an sync implementation repectively.
47+
an async and a sync implementation respectively.
4648

47-
## Macros in Detail
49+
### Macros in Detail
4850

4951
`maybe-async` offers 4 set of attribute macros: `maybe_async`,
5052
`sync_impl`/`async_impl`, `must_be_sync`/`must_be_async`, and `test`.
@@ -71,7 +73,7 @@ blocking code except for async/await keywords. And use feature gate
7173
maybe_async = "0.2"
7274
```
7375

74-
Wanna convert async code to sync? Add `maybe_async` to dependencies with
76+
Want to convert async code to sync? Add `maybe_async` to dependencies with
7577
an `is_sync` feature gate. In this way, `maybe_async` is the same as
7678
`must_be_sync`:
7779

@@ -80,19 +82,31 @@ blocking code except for async/await keywords. And use feature gate
8082
maybe_async = { version = "0.2", features = ["is_sync"] }
8183
```
8284

83-
Not all async traits need futures that are `dyn Future + Send`.
84-
To avoid having "Send" and "Sync" bounds placed on the async trait
85-
methods, invoke the maybe_async macro as #[maybe_async(?Send)] on both
86-
the trait and the impl blocks.
85+
There are three usage variants for `maybe_async` attribute macros:
86+
- `#[maybe_async]` or `#[maybe_async(Send)]`
87+
88+
In this mode, `#[async_trait::async_trait]` is added to trait declarations and trait implementations
89+
to support async fn in traits.
90+
91+
- `#[maybe_async(?Send)]`
92+
93+
Not all async traits need futures that are `dyn Future + Send`.
94+
In this mode, `#[async_trait::async_trait(?Send)]` is added to trait declarations and trait implementations,
95+
to avoid having "Send" and "Sync" bounds placed on the async trait
96+
methods.
8797

98+
- `#[maybe_async(AFIT)]`
99+
100+
AFIT is acronym for **a**sync **f**unction **i**n **t**rait, stabilized from rust 1.74
88101

89102
- `must_be_async`
90103

91-
**Keep async**. Add `async_trait` attribute macro for trait declaration
92-
or implementation to bring async fn support in traits.
104+
**Keep async**.
93105

94-
To avoid having "Send" and "Sync" bounds placed on the async trait
95-
methods, invoke the maybe_async macro as #[must_be_async(?Send)].
106+
There are three usage variants for `must_be_async` attribute macros:
107+
- `#[must_be_async]` or `#[must_be_async(Send)]`
108+
- `#[must_be_async(?Send)]`
109+
- `#[must_be_async(AFIT)]`
96110

97111
- `must_be_sync`
98112

@@ -102,56 +116,68 @@ blocking code except for async/await keywords. And use feature gate
102116

103117
- `sync_impl`
104118

105-
An sync implementation should on compile on blocking implementation and
106-
must simply disappear when we want async version.
119+
A sync implementation should compile on blocking implementation and
120+
must simply disappear when we want async version.
107121

108122
Although most of the API are almost the same, there definitely come to a
109123
point when the async and sync version should differ greatly. For
110124
example, a MongoDB client may use the same API for async and sync
111-
verison, but the code to actually send reqeust are quite different.
125+
version, but the code to actually send reqeust are quite different.
112126

113127
Here, we can use `sync_impl` to mark a synchronous implementation, and a
114-
sync implementation shoule disappear when we want async version.
128+
sync implementation should disappear when we want async version.
115129

116130
- `async_impl`
117131

118132
An async implementation should on compile on async implementation and
119-
must simply disappear when we want sync version.
120-
121-
To avoid having "Send" and "Sync" bounds placed on the async trait
122-
methods, invoke the maybe_async macro as #[async_impl(?Send)].
133+
must simply disappear when we want sync version.
123134

135+
There are three usage variants for `async_impl` attribute macros:
136+
- `#[async_impl]` or `#[async_impl(Send)]`
137+
- `#[async_impl(?Send)]`
138+
- `#[async_impl(AFIT)]`
124139

125140
- `test`
126141

127142
Handy macro to unify async and sync **unit and e2e test** code.
128143

129144
You can specify the condition to compile to sync test code
130145
and also the conditions to compile to async test code with given test
131-
macro, e.x. `tokio::test`, `async_std::test` and etc. When only sync
146+
macro, e.x. `tokio::test`, `async_std::test`, etc. When only sync
132147
condition is specified,the test code only compiles when sync condition
133148
is met.
134149

135150
```rust
136-
#[maybe_async::test(
151+
# #[maybe_async::maybe_async]
152+
# async fn async_fn() -> bool {
153+
# true
154+
# }
155+
156+
##[maybe_async::test(
137157
feature="is_sync",
138-
async(all(not(feature="is_sync"), feature="async_std"), async_std::test),
139-
async(all(not(feature="is_sync"), feature="tokio"), tokio::test)
158+
async(
159+
all(not(feature="is_sync"), feature="async_std"),
160+
async_std::test
161+
),
162+
async(
163+
all(not(feature="is_sync"), feature="tokio"),
164+
tokio::test
165+
)
140166
)]
141167
async fn test_async_fn() {
142168
let res = async_fn().await;
143169
assert_eq!(res, true);
144170
}
145171
```
146172

147-
## What's Under the Hook
173+
### What's Under the Hook
148174

149175
`maybe-async` compiles your code in different way with the `is_sync` feature
150-
gate. It remove all `await` and `async` keywords in your code under
176+
gate. It removes all `await` and `async` keywords in your code under
151177
`maybe_async` macro and conditionally compiles codes under `async_impl` and
152178
`sync_impl`.
153179

154-
Here is an detailed example on what's going on whe the `is_sync` feature
180+
Here is a detailed example on what's going on whe the `is_sync` feature
155181
gate set or not.
156182

157183
```rust
@@ -252,19 +278,21 @@ fn maybe_async_fn() -> Result<(), ()> {
252278
}
253279
```
254280

255-
## Examples
281+
### Examples
256282

257-
### rust client for services
283+
#### rust client for services
258284

259285
When implementing rust client for any services, like awz3. The higher level
260286
API of async and sync version is almost the same, such as creating or
261-
deleting a bucket, retrieving an object and etc.
287+
deleting a bucket, retrieving an object, etc.
262288

263289
The example `service_client` is a proof of concept that `maybe_async` can
264290
actually free us from writing almost the same code for sync and async. We
265291
can toggle between a sync AWZ3 client and async one by `is_sync` feature
266292
gate when we add `maybe-async` to dependency.
267293

268294

269-
# License
270-
MIT
295+
## License
296+
MIT
297+
298+
License: MIT

Diff for: examples/service_client.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ type Method = String;
99

1010
/// InnerClient are used to actually send request,
1111
/// which differ a lot between sync and async.
12-
#[maybe_async::maybe_async]
12+
///
13+
/// Use native async function in trait
14+
#[maybe_async::maybe_async(AFIT)]
1315
trait InnerClient {
1416
async fn request(method: Method, url: Url, data: String) -> Response;
1517
#[inline]
@@ -38,7 +40,7 @@ impl InnerClient for ServiceClient {
3840
}
3941

4042
/// Asynchronous implementation, only compiles when `is_sync` feature is off.
41-
#[maybe_async::async_impl]
43+
#[maybe_async::async_impl(AFIT)]
4244
impl InnerClient for ServiceClient {
4345
async fn request(method: Method, url: Url, data: String) -> Response {
4446
// your implementation for async, like use `reqwest::client`

0 commit comments

Comments
 (0)