Reputation: 2737
I am trying to use a channel to communicate between an event handler and the main thread of my program using async rust. The event handler in question is this one from the matrix-rust-sdk.
I can see that this exact pattern is used in the code here.
But when I tried literally the same thing in my own code, it gives me a really strange lifetime error.
error: lifetime may not live long enough
--> src/main.rs:84:13
|
80 | move |event: OriginalSyncRoomMessageEvent, room: Room| {
| ------------------------------------------------------
| | |
| | return type of closure `impl futures_util::Future<Output = ()>` contains a lifetime `'2`
| lifetime `'1` represents this closure's body
...
84 | / async move {
85 | | if let Room::Joined(room) = room {
86 | | if room.room_id() == room_id {
87 | | match event.content.msgtype {
... |
94 | | }
95 | | }
| |_____________^ returning this value requires that `'1` must outlive `'2`
|
= note: closure implements `Fn`, so references to captured variables can't escape the closure
I tried to make a much simpler example, and the weird lifetime error remains:
use tokio::sync::mpsc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let (tx, _rx) = mpsc::channel(32);
let closure = move || async {
at.send("hi");
};
Ok(())
}
Gives me:
error: lifetime may not live long enough
--> src/main.rs:9:27
|
9 | let closure = move || async {
| ___________________-------_^
| | | |
| | | return type of closure `impl Future<Output = ()>` contains a lifetime `'2`
| | lifetime `'1` represents this closure's body
10 | | at.send("hi");
11 | | };
| |_____^ returning this value requires that `'1` must outlive `'2`
|
= note: closure implements `Fn`, so references to captured variables can't escape the closure
So how can I use a channel in an async closure? Why doesn't my code work when the code in the matrix-rust-sdk does?
Upvotes: 0
Views: 827
Reputation: 2737
So I finally fixed the error. It turns out that something in Room
doesn't implement Copy
and therefore it was causing some sort of state sharing despite the Clone
s. I fixed it by passing the RoomId
as a string. Since the lifetime error message is entirely opaque, there was no way to see which moved variable was actually causing the problem. Off to file a compiler bug report.
Upvotes: 0
Reputation: 22893
I think you meant || async move
instead of move || async
.
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
let (tx, _rx) = mpsc::channel(32);
let closure = || async move {
tx.send("hi").await.unwrap();
};
closure().await;
}
I think in most cases, |args| async move {}
is what you want to use if you want to create an async
closure. But I don't completely understand the differences either.
For more infos, this might help: What is the difference between `|_| async move {}` and `async move |_| {}`.
I don't think your minimal example represents your actual problem of your real code, though. This is a minimal example that represents the real problem:
#[derive(Clone, Debug)]
struct RoomId(u32);
#[derive(Clone, Debug)]
struct Room {
id: RoomId,
}
impl Room {
fn room_id(&self) -> &RoomId {
&self.id
}
}
#[tokio::main]
async fn main() {
let dm_room = Room { id: RoomId(42) };
let dm_room_closure = dm_room.clone();
let closure = move || {
let room_id = dm_room_closure.room_id();
async move {
println!("{}", room_id.0);
}
};
closure().await;
}
error: lifetime may not live long enough
--> src/main.rs:23:9
|
20 | let closure = move || {
| -------
| | |
| | return type of closure `impl Future<Output = ()>` contains a lifetime `'2`
| lifetime `'1` represents this closure's body
...
23 | / async move {
24 | | println!("{}", room_id.0);
25 | | }
| |_________^ returning this value requires that `'1` must outlive `'2`
|
= note: closure implements `Fn`, so references to captured variables can't escape the closure
The real problem here is caused by the fact that room_id
contains a reference to dm_room_closure
, but dm_room_closure
does not get kept alive by the innermost async move
context.
To fix this, make sure that the async move
keeps dm_room_closure
alive by moving it in as well. In this case, this is as simple as creating the room_id
variable inside of the async move
:
#[derive(Clone, Debug)]
struct RoomId(u32);
#[derive(Clone, Debug)]
struct Room {
id: RoomId,
}
impl Room {
fn room_id(&self) -> &RoomId {
&self.id
}
}
#[tokio::main]
async fn main() {
let dm_room = Room { id: RoomId(42) };
let dm_room_closure = dm_room.clone();
let closure = move || {
async move {
let room_id = dm_room_closure.room_id();
println!("{}", room_id.0);
}
};
closure().await;
}
42
Upvotes: 3