It solves my example, but doesn't allow me to capture the environment. (My fault for making a too simple example.)
impl<T> Nop<T>
where
T: Message + Send + 'static,
{
/// Creates a block which does nothing but pass data through
pub fn new() -> Self {
let (mut receiver, receiver_connector) = new_receiver::<T>();
let (sender, sender_connector) = new_sender::<T>();
spawn(async move {
loop {
let Ok(msg) = receiver.recv().await else { return; };
// more stuff goes here, of which some needs access to
// arguments passed to the `new` method later
let Ok(()) = sender.send(msg).await else { return; };
}
});
Self {
receiver_connector,
sender_connector,
}
}
}
This is more how it looks like, with at least sender
and receiver
being captured by the async move
. In many cases I have more variables captured.
If I use a function, I cannot capture the environment.
An older (real life) version of the code demonstrating where I need this is here (edit: or look there for a more complex example). However, certain interfaces will change with the next version, so the above pasted snippet is closer to what I need (but I can't easily make a Playground example of either of it).
Side note: Switching to the let-else statements led to rustfmt
not working properly yet (issue #4914 of rustfmt
), i.e. it doesn't format the let-else statements yet.
I also tried this, but then have problems with futures not being Send
:
async fn foo() -> Result<(), Box<dyn std::error::Error>> {
Err("some error")?;
Ok(())
}
#[tokio::main]
async fn main() {
let join_handle = tokio::spawn(async move {
loop {
foo().await?;
}
#[allow(unreachable_code)]
Ok::<(), Box<dyn std::error::Error>>(())
});
join_handle.await.unwrap().ok();
}
(Playground)
Errors:
Compiling playground v0.0.1 (/playground)
error[E0277]: `dyn std::error::Error` cannot be sent between threads safely
--> src/main.rs:8:36
|
8 | let join_handle = tokio::spawn(async move {
| _______________________------------_^
| | |
| | required by a bound introduced by this call
9 | | loop {
10 | | foo().await?;
11 | | }
12 | | #[allow(unreachable_code)]
13 | | Ok::<(), Box<dyn std::error::Error>>(())
14 | | });
| |_____^ `dyn std::error::Error` cannot be sent between threads safely
|
= help: the trait `Send` is not implemented for `dyn std::error::Error`
= note: required for `Unique<dyn std::error::Error>` to implement `Send`
= note: required because it appears within the type `Box<dyn std::error::Error>`
= note: required because it appears within the type `Result<(), Box<dyn std::error::Error>>`
note: required by a bound in `tokio::spawn`
--> /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.21.2/src/task/spawn.rs:128:20
|
128 | T::Output: Send + 'static,
| ^^^^ required by this bound in `tokio::spawn`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to previous error
I guess I could just pass all variables to a function, but this feels pretty verbose:
#[tokio::main]
async fn main() {
let some_variable = ();
let some_other_variable = ();
let join_handle = tokio::spawn(async move {
async fn inner(_some_variable: (), _some_other_variable:()) -> Result<(), Box<dyn Error>> {
loop {
foo().await?;
bar().await?;
}
}
inner(some_variable, some_other_variable).await.ok()
});
join_handle.await.unwrap();
}
(Playground)
Also, this performs an unnecessary heap allocation if I don't actually deal with boxed errors:
impl std::error::Error for SomeError {}
impl std::error::Error for AnotherError {}
async fn foo() -> Result<(), SomeError> {
Err(SomeError)?;
Ok(())
}
async fn bar() -> Result<(), AnotherError> {
Ok(())
}
None of the variants really seem to be nice, and so far using let Ok(()) = … else { return; };
seems to be most concise .