asynchronousrust

Async closures and &mut borrow


Rust 1.85 stabilized async closures https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/#async-closures

They have nice example, basically

async fn example() {
    let mut vec: Vec<String> = vec![];

    let mut closure = async || {
        vec.push(std::future::ready(String::from("")).await);
    };

    let res = closure().await;
    println!("example: vec = {:?}", vec);
}

However if I make the example just slightly more complex (useful?)

async fn example_modified() {
    let mut vec: Vec<String> = vec![];
    let data = vec!["1","2","3","4"];

    let mut closure = async |d: &str| {
        vec.push(std::future::ready(String::from("")).await);
    };

    let futures = data.into_iter().map(closure); // will not compile because of this line

    futures::future::join_all(futures).await;

    println!("example: vec = {:?}", vec);
}

I get

error: async closure does not implement `FnMut` because it captures state from its environment
   --> src/async_fnmut.rs:69:23
    |
69  |     let mut closure = async |d: &str| {
    |                       ^^^^^^^^^^^^^^^
...
73  |     let futures = data.into_iter().map(closure); // will not compile because of this line
    |                                    --- required by a bound introduced by this call
    |
note: required by a bound in `std::iter::Iterator::map`
   --> /Users/martin/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:748:12
    |
745 |     fn map<B, F>(self, f: F) -> Map<Self, F>
    |        --- required by a bound in this associated function
...
748 |         F: FnMut(Self::Item) -> B,
    |            ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Iterator::map`

Can you explain why?


Solution

  • The main problem is that the future that your closure returns needs an exclusive reference to vec so it can push values to it.

    To prevent mulitple exclusive references existing in parallel you cannot be allowed to call the closure again before you've awaited the returned future. That contract however isn't enforcable with a FnMut therefore async closures that capture a mutable reference in their returned future cannot implement FnMut. They do implement AsyncFnMut instead which allows to return borrows to the closure.

    Iterator::map requires a FnMut so you can't use it with that closure.

    You can simply wrap that mutable reference in a RefCell (or Mutex in multithreaded environments) to get around this limitation:

    use std::cell::RefCell;
    #[tokio::main]
    async fn main() {
        let mut vec: Vec<String> = vec![];
        let data = vec!["1", "2", "3", "4"];
    
        {
            let vec = RefCell::new(&mut vec);
            let mut closure = async |d: &str| {
                vec.borrow_mut()
                    .push(std::future::ready(String::from("")).await);
            };
    
            let futures = data.into_iter().map(closure);
    
            futures::future::join_all(futures).await;
        }
    
        println!("example: vec = {:?}", vec);
    }
    
    

    Playground