Expand description
enjoin - async joining at the syntax level
The macros provided in this crate take blocks of code and run them concurrently. The macros try to make the blocks work in the same way that regular blocks (that are not being run concurrently) work.
Syntax
let (res_1, res_2) = enjoin::join!(
{
// Code goes here
},
{
// Code goes here
}
);
The macro takes blocks of code and execute them concurrently. Any number of blocks may be supplied.
The blocks are regular blocks, not async blocks.
The blocks can contain async code (i.e. you can use .await
in the blocks).
The results are returned as a tuple.
Features
This features are things that you can already do in regular blocks. They are being listed here because they don’t work in async blocks (which other join implementations rely on).
Branching statements support
You can use break
, continue
, and return
inside the blocks.
let x = 'a: loop {
enjoin::join!(
{
// do something
break 'a 5;
},
{
// do something else
continue;
},
{
// do something
return;
}
);
};
The break
/continue
may be labeled or unlabeled.
break
-ing with a value is supported.
You can, of course, still use break
and continue
for
loops or blocks contained inside the join macro.
As in regular Rust, unlabeled break
/return
effects
the innermost loop,
and labeled break
/return
effects the innermost loop with that label.
Returning from inside the join will cause the current function to return.
If the branching statement causes execution to jump out of the macro, all the code executing in the macro will be stopped and dropped.
Try operator (?
) support
You can use the ?
operator inside your blocks just as you would outside
a join macro.
async fn f() -> Result<i32, &'static str> {
enjoin::join!(
{
let a = Ok(5);
let b = Err("oh no");
let c = a?;
b?; // causes the `f` function to return `Err("oh no")`
},
{
// ...
}
);
unreachable!("would have returned error at `b?`")
}
Shared borrowing support
If two or more blocks mutably borrow the same value, the join_auto_borrow!
macro will automatically put that value in a RefCell.
(join_auto_borrow!
also do everything join!
does)
let mut count = 0;
enjoin::join_auto_borrow!(
{
// ...
count += 1;
},
{
count -= 1;
}
);
The macro makes sure the RefCell will never panic by disallowing shared borrows from lasting across await yieldpoints.
let mut count = 0;
enjoin::join_auto_borrow!(
{
let borrow = &mut count;
std::future::ready(123).await; // <- borrow crosses this yieldpoint
drop(borrow);
},
{
count += 1;
}
);
More information
There is a blog post detailing why this macro was made and how it works.
The source code is here on GitHub.
Troubleshooting
-
If branching statements and/or captured variables are hidden in another macro, enjoin wouldn’t be able to transform them. This will usually cause compilation failure.
enjoin::join!({ vec![ 1, 2, 3 // enjoin can't see the code in this vec! ] });
- If an
await
is hidden inside a macro,join_auto_borrow!
won’t be able to unlock the RefCell for the yieldpoint, leading to a RefCell panic. This limitation means you can’t nestenjoin::join!
ortokio::join!
withinenjoin::join_auto_borrow!
.
- With only syntactic information, enjoin can only guess whether or not a
name is a borrowed variable, and whether or not that borrow is mutable.
We have heuristics, but even so the macro may end up RefCell-ing
immutable borrows, constants, or function pointers sometimes.
You can help the macro by writing
(&mut var).method()
or(&var).method()
instead ofvar.method()
.
Modules
- Polyfill for the rust Try (and related) trait that is currently unstable. See https://doc.rust-lang.org/std/ops/trait.Try.html for docs. Any divergences in behaviour should be considered bugs.
Macros
- Run given blocks of async code concurrently. Use
break
/continue
/return
/?
to jump out. See the crate documentation. - Everything join! does, plus the automatic shared mutable borrowing described in the crate documentation.