How using Mutex fix `FnMut` cannot allow references to captured variables to escape?

I am trying to write Ping message to a async websocket:

        let (mut write, read) = ws_stream.split();
        let interval = tokio::time::interval(Duration::from_secs(10));
        let ping = interval
            .for_each(|_| async {
                write.send(Message::Ping(vec![]));
            })
            .await;

Got error message:

81 |               .for_each(|_| async {
   |  _________________________-_^
   | |                         |
   | |                         inferred to be a `FnMut` closure
82 | |                 write.send(Message::Ping(vec![]));
83 | |             })
   | |_____________^ returns a reference to a captured variable which escapes the closure body
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape

But adding a Mutex fixed the problem:

        let (mut write, read) = ws_stream.split();
        let write = Mutex::new(write);
        let interval = tokio::time::interval(Duration::from_secs(10));
        let ping = interval
            .for_each(|_| async {
                write.lock().unwrap().send(Message::Ping(vec![]));
            })
            .await;

Could someone explain to me why this error message happens, and why adding Mutex fixes the problem?
I am even more confuse to find out adding a move make the above code not compile again:

        let (mut write, read) = ws_stream.split();
        let write = Mutex::new(write);
        let interval = tokio::time::interval(Duration::from_secs(10));
        let ping = interval
            .for_each(move |_| async {
                write.lock().unwrap().send(Message::Ping(vec![]));
            })
            .await;

Error:

error: captured variable cannot escape `FnMut` closure body
   |
82 |               .for_each(move |_| async {
   |  ______________________________-_^
   | |                              |
   | |                              inferred to be a `FnMut` closure
83 | |                 write.lock().unwrap().send(Message::Ping(vec![]));
84 | |             })
   | |_____________^ returns a reference to a captured variable which escapes the closure body
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape

A Mutex can modify things from behind a shared reference, so the closure get's inferred to be a Fn closure. Normally, you can't mutate things behind a shared reference, so you are required to use a mutable (exclusive) reference, which means that the closure get's inferred to be FnMut

1 Like

In this case you should use a while let loop. The for_each stream combinator does not allow modification of variables, from outside the loop.

while let Some(_) = interval.next() {
    write.send(Message::Ping(vec![]));
}
1 Like