Macro async_fn::before_async [−][src]
macro_rules! before_async {
(@) => { ... };
($($_ : tt) *) => { ... };
}
Expand description
Helper macro to express eagerly executed code (before the async
suspension) when inside #[async_fn::bare_future]
-annotated function.
Eager vs. lazy / suspended code?
Consider:
use ::async_fn::prelude::*;
/// Consider:
async fn lazy_print() -> i32 {
println!("Hi");
42
}
/// i.e.,
fn lazy_print_unsugared() -> impl Fut<'static, i32> {
/* return */ async move { // <- lazy future / suspension point
println!("Hi"); // <- this runs _after_ returning
42
}
}
/// vs.
fn eager_print() -> impl Fut<'static, i32> {
println!("Hi"); // <- this runs _before_ returning
/* return */ async move { // <- lazy future / suspension point
42
}
}
Both lazy_print().await
and eager_print().await
shall behave the same
insofar that they’ll both print "Hi"
and then resolve to 42
.
That being said, when doing:
let future = mk_future();
println!("Bye");
assert_eq!(future.await, 42);
then, depending on whether mk_future
refers to eager_print
or
lazy_print
, the println!("Hi")
statement will, respectively, be
executed before returning the future or be part of the not-yet-polled
future. That is, "Hi"
will be printed before "Bye"
or after.
While this may look like a contrived example, when future
is spawned /
to be polled within a parallel executor (e.g.,
mt_executor::spawn(mk_future()); println!("Bye");
), then in the eager case
we’ll have the "Hi"
statement definitely occur before the "Bye"
statement, whereas in the lazy case there will be no clear ordering between
the two: "Hi"
and "Bye"
could appear in any order, or even intermingled!
Now, imagine if "Hi"
were a dereference of a short-lived borrow, (such as
let x = *borrowed_integer;
or Arc::clone(borrowed_arc)
), and if
"Bye"
were a statement dropping the borrowee. While in the eager case we’d
have a clear happens-before relationship that’d guarantee soundness, in the
lazy case we wouldn’t have it, and it would thus be very well possible to
suffer from race conditions or a use-after-free! In Rust this means we’ll
hit borrow-checker errors.
So this whole thing has to do with lifetimes:
-
either the returned future is short-lived; this is the case of:
-
async fn …(borrowed_arc: &Arc<…>) -> i32
-
or
fn …_unsugared(borrowed_arc: &'_ Arc<…>) -> impl Fut<'_, i32>
, )
Such futures are thus incompatible with a long-lived
spawn()
. -
-
or the future is to be compatible with long-lived
spawn()
s (it must be'static
, and often, alsoSend
); this is the case of:fn …(borrowed_arc: &'_ Arc<…>) -> impl Fut<'static, i32>
and for that to actually pass borrowck any dereference in the
fn
body (such asArc::clone(borrowed_arc)
) has to be done eagerly:ⓘfn some_future(borrowed_arc: &'_ Arc<Stuff>) -> impl Fut<'static, Ret> { let owned_arc = Arc::clone(borrowed_arc); async move /* owned_arc */ { stuff(&owned_arc).await } }
But in the case of a #[async_fn::bare_future] async fn
, the whole function body
is automagically wrapped within an async
suspension!
That’s thus the purpose of this macro:
Usage
When inside the body of a #[async_fn::bare_future]
async fn
, this macro can be
called as the very first statement of the function’s body, with
statements inside it (any other usage is an error, or might error in future
semver-compatible releases ⚠️).
That will make the statements be executed eagerly / before the async
suspension (hence the name of the macro).
use ::async_fn::prelude::*;
use ::std::sync::Arc;
struct FooInner { /* … */ }
impl FooInner {
async fn stuff(&self) -> Result<i32, Error> {
/* … */
}
}
pub struct Foo {
inner: Arc<FooInner>,
}
impl Foo {
#[async_fn::bare_future]
pub async fn stuff(&self) -> impl Fut<'static, Result<(), Error>> {
before_async! {
let inner = Arc::clone(&self.inner);
}
let x = inner.stuff().await?; // <- await point.
println!("{}", x);
Ok(())
}
}