Borrowing in Async Rust

While learning to use async in rust I tripped up a little bit on the behavior of borrowing and lifetimes. This is meant to summarize how borrows behave when used in async functions and methods.

This is written with some assumption that the reader is familiar with borrows and has some cursory knowledge of async/await.

Normal Borrowing

Normally a borrow persists until the completion of a function. This can often be read fairly linearly through the code.

The comments in main() specify when the borrows start and stop.

Since no new structs or similar are being derived from the borrow of Foo the borrows end after each method. This means that the mutable borrow can start after the immutable borrow because the immutable borrow has ended.

Async Borrowing

In async code a function or method returns a future. This future encapsulates everything that is part of the function.

Compiling the following code will fail.

The compilation error should look like:

error[E0502]: cannot borrow `foo` as mutable because it is also borrowed as immutable
  --> src/main.rs:10:5
   |
6  |     let future = foo.an_async_borrow_of_self();
   |                  ----------------------------- immutable borrow occurs here
...
10 |     foo.a_mutable_borrow_of_self();
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
...
14 |     future.await;
   |     ------ immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.

The compiler is saying that the &self parameter of Foo::an_async_borrow_of_self() is still being borrowed. This is because it’s borrowed as part of future_1.

If we await the future prior to the mutable borrow the program will compile and run. This is because the borrow propagates to the future. Once we await on the future we fully consume it and are done. This means the compiler is free to drop it there, ending the borrow of foo.

The code snippets provide print statements around the method invocation and the awaiting of the future. This is done to emphasize that async functions do nothing until they are awaited on.

When learning async I kept having to reaffirm this to myself.

Summary

The function signatures don’t show it, but the future that is returned has the same borrow usage as the function arguments. A function that mutably borrows an argument will return a future that mutably borrows that argument. The mutable borrow will last until the future is fully consumed.