Crates.io | async_closure |
lib.rs | async_closure |
version | 0.1.2 |
source | src |
created_at | 2023-03-01 14:25:20.498876 |
updated_at | 2023-03-30 13:27:49.208864 |
description | This crate utilizes the nightly-only feature async_fn_in_trait to imitate async_closures. |
homepage | |
repository | https://github.com/zjp-CN/async_closure |
max_upload_size | |
id | 798041 |
size | 48,615 |
async_closure
This crate utilizes the nightly-only feature async_fn_in_trait
to imitate async_closures.
You don't have to Box the return Future from a local closure in async code this time!
The steps to use this crate:
Choose an async trait which is used in trait bounds
capture_no_lifetimes
] mod when you're sure there won't be any temporarily
referenced type to be captured (i.e. all captured types statisfy 'static
bound).capture_lifetimes
] mod.// e.g. take a closure that potentially captures references and will change its states.
// 0. import the `AsyncFnMut` trait and companion macro `async_closure_mut`
#![feature(async_fn_in_trait)]
#![allow(incomplete_features)]
use async_closure::{capture_lifetimes::AsyncFnMut, async_closure_mut};
// 1. Adjust your caller where the trait bound looks like the following
async fn take_a_mut_closure<'env, T, F>(mut cb: F) -> T
where
// 'env means the lifetime of captured variables outside the function
// 'any means the lifetime of arguments coming from inside the function
// also note how we express and represent one argument via `(arg1, )`,
// likewise we can declare more arguments via `(arg1, arg2, arg3, )` etc.
F: for<'any> AsyncFnMut<'env, (&'any str,), Output = T>,
{
let mut s = String::from("-");
let args = (&s[..],); // type inference doesn't work well here
cb.call_mut(args).await;
s.push('+');
let args = (&s[..],);
cb.call_mut(args).await
}
Use its companion macro to auto generate a value, the type of which is distinct and unnamed but implemented with the trait. The syntax in macros contains two parts:
For capture_lifetimes style macros:
{}
where multiple assignments field_name: field_type = field_value
seperated by ,
are declaredasync |arg1: arg1_type, ...| -> return_type { /* any async code here */ }
Note: AsyncFn*
family only contains single lifetime parameter 'a
, and you must use it in field_type
and return_type
to express the non-static referenced type. But if the type there doesn't contain a reference, 'a
is needless.
let (outer, mut buf, buffer) = (String::new(), String::new(), String::new());
// 2. Define a capturing closure
let cb1 = async_closure_mut!({
s: &'a str = &outer, // shared reference
buf: &'a mut String = &mut buf, // mutable reference
arc: Arc<str> = buffer.into(), // owned type without explicit mutation
len: usize = 0, // owned type with mutation, see the code below
}; async |arg: &str| -> Result<usize, ()>
// Annotate both inputs and output: no lifetime parameter on arguments' type!
{
// Write async code here, using the field names and argument names as variables.
// Note: the type of fields here depends on `AsyncFn*`.
// i.e. for this macro, all field comes from `let Self { pattern_match } = &mut self;`
// thus `s: &mut &'a str`, `buf: &mut &'a mut String` etc.
// If you use `async_closure!`, then `s: &&'a str`, `buf: &&'a mut String` etc.
// If you use `async_closure_once!`, then `s: &'a str`, `buf: &'a mut String` etc.
Ok(*len)
});
'a
field_type
and return_type
can't be temporary referenced typesPass the value into the caller function
take_a_mut_closure(cb1).await.unwrap(); // That's it :)
macro | trait | capture references | mutate fields | times to be used |
---|---|---|---|---|
[async_closure! ] |
[capture_lifetimes::AsyncFn ] |
√ | × | no limit |
[async_closure_mut! ] |
[capture_lifetimes::AsyncFnMut ] |
√ | √ | no limit |
[async_closure_once! ] |
[capture_lifetimes::AsyncFnOnce ] |
√ | √ | 1 |
[async_owned_closure! ] |
[capture_no_lifetimes::AsyncFn ] |
× | × | no limit |
[async_owned_closure_mut! ] |
[capture_no_lifetimes::AsyncFnMut ] |
× | √ | no limit |
[async_owned_closure_once! ] |
[capture_no_lifetimes::AsyncFnOnce ] |
× | √ | 1 |
MSRV: v1.69.0, and nightly-only due to the async_fn_in_trait
feature.
To avoid boxing the return Future from a local closure as I said.
Try this crate if you're not statisfied with the traditional approaches as discussed here. But they do work on stable Rust. If you're not familiar, it's worth reading.
If you can use async_fn_in_trait
feature, of course you probably define a custom trait with
meaningful method calls. But it also means to define context-based structs that are hardly used twice.
So this crate can generate these structs behind the scenes to reduce boilerplate code.
And an advantage over closures is you're able to keep the (non-once) structs alive as long as you want.
async fn take_and_return_a_mut_closure<'env, T, F>(mut cb: F) -> (T, F)
where
F: for<'any> AsyncFnMut<'env, (&'any str,), Output = T>,
{
let s = String::from("-");
(cb.call_mut((&s[..],)).await, cb) // Note: return the closure type
}
async fn test4() {
let mut buf = String::new();
let cb = async_closure_mut!({
buf: &'a mut String = &mut buf
}; async |arg: &str| -> () {
buf.push_str(arg);
});
let (_output, cb_again) = take_and_return_a_mut_closure(cb).await;
cb_again.buf.push('+'); // Still use it
assert_eq!(cb_again.buf, "-+");
take_a_mut_closure(cb_again).await; // And pass it into another function
// Note: since AsyncFnMut is the subtrait to AsyncFnOnce,
// you can pass it into a fucntion that requires AsyncFnOnce
// as long as they have identical generic parameters.
}
Impossible for now. See the second question above that gives a link to show traditional well-known stable ways, especially for non-capturing async callbacks/functions.
Yes. Each trait and macro has its own usecase in details. Also have a look at examples folder which comes from URLO as a solution to some snippet question.