timthelion
timthelion

Reputation: 2737

How can I use a channel between an async closure and my main thread in rust?

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

Answers (2)

timthelion
timthelion

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 Clones. 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

Finomnis
Finomnis

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

Related Questions