Understanding an unexpected async-fn borrow checker error

The following code results in this error:

error[E0502]: cannot borrow *self as mutable because it is also borrowed as immutable

struct Cache {
    data: Option<Data>,
}

struct Data {}

impl Cache {
    async fn get(&mut self) -> &Data {
        loop {
            if let Some(data) = &self.data {
                return data;
            }

            self.update().await;
        }
    }

    async fn update(&mut self) {
        todo!("update `self.data` if possible")
    }
}

Replacing Some(data) = &self.data with Some(ref data) = self.data makes the code compile!

Why does the original not work? (I guess it relates to what the scope of the immutable borrow is assumed to be, namely across the await point, even though it logically should not be?)

And why exactly does changing to ref help?

Please point me to documentation that explains this, if possible.

Thanks!

The async part is not relevant; you will get the same error with async and await deleted. This is the borrow checker not fully understanding uses of borrows in conditional control flow. It sees that the usage of &self.data potentially extends “to the end of the function body” (if returned), and therefore conflicts with the self.update() borrow occurring later in the function body; it doesn’t see that those two cases are mutually exclusive because the if cannot both return and continue.

The workaround is to avoid taking the borrow that you will return until you know for certain that you are going to return it; this is what using ref data does, because it delays the borrowing of self.data until after the match succeeds.

1 Like