RwLock is confusing me and/or mutable borrow counting

I've hit a problem that asked me to read the docs at

rustc --explain E0499

aka "A variable was borrowed as mutable more than once".

But when trying to minimise the problem, I don't understand why my code is failing anymore.

Let's start on one end of the spectrum, with a simple example that does seem to compile. I have a struct with multiple fields inside, and I want to pass those fields to a function that can mutate them.

struct Foo {
    a: String,
    b: String,
}

pub fn thing_with_foo() {
    let mut foo = Foo {
        a: "a".to_string(),
        b: "b".to_string(),
    };

    thing_with_strings(&mut foo.a, &mut foo.b)
}

pub fn thing_with_strings(a: &mut String, b: &mut String) {}

Unless that's doing some magic copying, that looks like the compiler actually understands that the two fields are being independently mutated and that the foo is doesn't have a double mutation leak.

But now let's say we're accessing Foo from an async_rwlock::RwLock instead (presumably the same will apply for the sync version)

pub async fn thing_with_foo() {
    let foo = Foo {
        a: "a".to_string(),
        b: "b".to_string(),
    };

    let locked = RwLock::new(foo);
    {
        let mut bar = locked.write().await;

        thing_with_strings(&mut bar.a, &mut bar.b);
    }
}

now my compile fails with

37 |         thing_with_strings(&mut bar.a, &mut bar.b);
   |         ------------------      ---         ^^^ second mutable borrow occurs here
   |         |                       |
   |         |                       first mutable borrow occurs here
   |         first borrow later used by call

have I done something horribly wrong here?

The RwLock is dropped at the end of the block. I added an explicit block here to make that clear. bar is actually a RwLockWriteGuard<'_, Foo> although it mostly behaves like a Foo. I tried shadowing the bar variable here (which doesn't cause the lock to be dropped, because shadowing doesn't remove it from the scope) to remove the complexity introduced by the guard forwarding the methods but I wasn't able to figure out how to do that.

Also I tried splitting out the fields into separate variables, but that also fails

        let mut a = &mut bar.a;
        let mut b = &mut bar.b;

36 |         let mut a = &mut bar.a;
   |                          --- first mutable borrow occurs here
37 |         let mut b = &mut bar.b;
   |                          ^^^ second mutable borrow occurs here

I'm not trying to borrow the struct twice, I just want to split up the contents of that struct into two separately mutable parts... I'm really confused why I can do that in my simple example but not when I introduce the RwLock into the equation.

Splitting borrows are hard for the compiler to figure out. You can nudge it with something like this:

use tokio::sync::RwLock;

struct Foo {
    a: String,
    b: String,
}

pub fn thing_with_strings(a: &mut String, b: &mut String) {}

pub async fn thing_with_foo() {
    let foo = Foo {
        a: "a".to_string(),
        b: "b".to_string(),
    };

    let locked = RwLock::new(foo);

    let mut bar = locked.write().await;

    let Foo {
        ref mut a,
        ref mut b,
    } = *bar;

    thing_with_strings(a, b);
}

Playground.

2 Likes

The compiler knows how to extract multiple &mut borrows from different fields of a &mut Foo, but accessing data through a lock guard has to go through DerefMut so you can only get one borrow at a time (because DerefMut::deref_mut() takes &mut self and could in principle mutate something when called).

There’s a simple but unobvious fix for this: reborrowing the lock guard to get a real &mut.

let mut bar = locked.write().await;
let bar: &mut Foo = &mut *bar;
thing_with_strings(&mut bar.a, &mut bar.b);

Or on one line:

let bar = &mut *locked.write().await;
thing_with_strings(&mut bar.a, &mut bar.b);
4 Likes

The issue is that bar is not actually a Bar or a &mut Bar but some other struct (often called "guard") that implement DerefMut with Target=Bar. The compiler will automatically introduce calls to deref_mut() whenever you try to use it as a &mut Bar, however each of these calls mutably borrows the whole guard, which means each new mutable access to your struct field will conflict with the old ones. There is however a simple fix: you can do let bar_mut_ref = &mut *bar; or let bar_mut_ref = bar.deref_mut(); to force the creation of a mutable reference from the guard, and then you'll be able to access disjoint fields on it.

5 Likes

oh wow, thanks everybody, I thought I was going crazy. That makes so much sense, and thank you for the code examples, I tried all of them and they work a treat. The magic of the DerefMut was really throwing me off, I think I will probably always reborrow the guard to shadow it since I'll almost never want access to the lock directly (I guess maybe only if I'm promoting or demoting the lock?).

The &mut * prefix works well, but I find the .deref_mut() approach to be easier to understand. Unfortunately I get

208 |         let mut bar = locked.write().await.deref_mut();
    |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^            - temporary value is freed at the end of this statement
    |                            |
    |                            creates a temporary value which is freed while still in use

if I try to use it, is there something extra needed there?

UPDATE

It works if I split it into two lines

let mut bar = locked.write().await;
let bar = bar.deref_mut();

The single let works with &mut * because there’s a special rule to support this case, temporary lifetime extension: if you write

let <var> = &<expr>;
// or
let <var> = &mut <expr>;

then the “temporaries” in <expr> (which in this case include the lock guard) are kept alive for the entire scope of <var>. (If they weren’t, this would always be a “dropped while borrowed” error, so instead the language gives it a convenient meaning.)

.deref_mut(), on the other hand, has no special privilege here. Personally, I also think that it is bad style to ever explicitly invoke the Deref or DerefMut traits — they should, in my opinion, only be used implicitly via &, *, and deref coercions.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.