Access to multiple attributes on a RwLock Struct

I have a struct in a Lock.
I need to call a function that depends on some fields in this struct, but I dont really find a way of doing it.

Example code:

use std::sync::RwLock;

enum MyInts {
    Short(i16),
    Int(i32),
}
struct MyStruct {
    int: MyInts,
    strings: Vec<String>,
}
impl MyStruct {
    pub fn get_mut_int(&mut self) -> &mut MyInts{
        &mut self.int
    }
}

fn modify_vec(v: &mut Vec<String>, int: i16) {
    for el in v.iter_mut() {
        *el += "ok";
    }
}

fn main() {
    let lock = RwLock::new(
        MyStruct {
            int: MyInts::Short(0),
            strings: vec![String::from("test")],
        }
    );

    let mut write = lock.write().unwrap();
    let int = write.get_mut_int();

    match &int {
        MyInts::Short(n) => {
            let strings = &mut write.strings;
            modify_vec(strings, *n);
        },
        MyInts::Int(n) => {
            // Something else...
        }
    }
}
error[E0499]: cannot borrow `write` as mutable more than once at a time
  --> main.rs:36:32
   |
32 |     let int = write.get_mut_int();
   |               ----- first mutable borrow occurs here
...
36 |             let strings = &mut write.strings;
   |                                ^^^^^ second mutable borrow occurs here
37 |             modify_vec(strings, *n);
   |                                 -- first borrow later used here

I understand the error, and I understand why it occurs, but I can't really find a way of going over it except by using .clone() which I would like to avoid.

(The code doesn't make sens, it's just to serve as an example)

Your example can be fixed—don't know if your real problem will be fixed by this as well as this relies on the fact that i16 is copy—by copying n before creating the mutable reference to write.strings, binding it to a variable and use that as parameter to modify_vec:

use std::sync::RwLock;

enum MyInts {
    Short(i16),
    Int(i32),
}
struct MyStruct {
    int: MyInts,
    strings: Vec<String>,
}
impl MyStruct {
    pub fn get_mut_int(&mut self) -> &mut MyInts{ 
        &mut self.int
    }
}

fn modify_vec(v: &mut Vec<String>, int: i16) {
    for el in v.iter_mut() {
        *el += "ok";
    }
}

fn main() {
    let lock = RwLock::new(
        MyStruct {
            int: MyInts::Short(0),
            strings: vec![String::from("test")],
        }
    );

    let mut write = lock.write().unwrap();
    let int = write.get_mut_int();

    match int {
        MyInts::Short(n) => {
+           let temp = *n;
            let strings = &mut write.strings;
-           modify_vec(strings, *n);
+           modify_vec(strings, temp);
        },
        MyInts::Int(n) => {
            // Something else...
        }
    }
}

Playground.

2 Likes

I mean, yes, it works! Thank you!
And you also underline the problem that that relays on the Copy trait.
Which is a good thing in this case, but in case of a more memory consuming struct, or just a struct from an external lib that doesn't implement Copy, it might be problematic :confused:

Example:

use std::sync::RwLock;

struct MyShort {
    val: i8,
}

enum MyInts {
    Short(MyShort),
    Int(i32),
}
struct MyStruct {
    int: MyInts,
    strings: Vec<String>,
}
impl MyStruct {
    pub fn get_mut_int(&mut self) -> &mut MyInts{
        &mut self.int
    }
}

fn modify_vec(v: &mut Vec<String>, short: &MyShort) {
    for el in v.iter_mut() {
        *el += "ok";
    }
}

fn main() {
    let lock = RwLock::new(
        MyStruct {
            int: MyInts::Short(MyShort { val: 0 }),
            strings: vec![String::from("test")],
        }
    );

    let mut write = lock.write().unwrap();
    let int = write.get_mut_int();

    match &int {
        MyInts::Short(n) => {
            let temp = *n;
            let strings = &mut write.strings;
            modify_vec(strings, &temp);
        },
        MyInts::Int(n) => {
            // Something else...
        }
    }
}

Thanks for your answer tho!

This looks like the classic problem described in After NLL: Interprocedural conflicts · baby steps There are a couple of ways to solve this which require you to restructure a bit your code, but they might not always be applicable and ultimately depend on what you're doing. That is to say, we need a bit more details if you want a proper solution.

1 Like

Another way to make borrowck understand your splitting borrows don't overlap would be something like this:

    let mut write = lock.write().unwrap();
    let MyStruct { ref mut int, ref mut strings } = *write;

    match int {
        MyInts::Short(n) => {
            modify_vec(strings, &n);
        },
        MyInts::Int(n) => {
            // Something else...
        }
    }

Playground.

But I'm just guessing again and as SkiFire13 said, without more details it will be hard to make well-informed suggestions.

1 Like