3 releases
0.1.2 | Mar 30, 2023 |
---|---|
0.1.1 | Mar 3, 2023 |
0.1.0 | Mar 1, 2023 |
#1175 in Asynchronous
35 downloads per month
21KB
221 lines
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
- use traits in
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). - otherwise, use traits in
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 traits in
-
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:
- a block
{}
where multiple assignmentsfield_name: field_type = field_value
seperated by,
are declared - an async closure
async |arg1: arg1_type, ...| -> return_type { /* any async code here */ }
Note:
AsyncFn*
family only contains single lifetime parameter'a
, and you must use it infield_type
andreturn_type
to express the non-static referenced type. But if the type there doesn't contain a reference,'a
is needless. - a block
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) });
- For non capture_lifetimes style macros, same components except that
- you can't use
'a
- i.e.
field_type
andreturn_type
can't be temporary referenced types
- i.e.
- you can't use
-
-
Pass 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 |
FAQ
- Requirement for Rust?
MSRV: v1.69.0, and nightly-only due to the async_fn_in_trait
feature.
- Why do I need this?
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.
}
- How to work on stable Rust?
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.
- More examples?
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.