Borrowing multiple fields of one structure

I have a struct with multiple vectors and I want to change one of them while iterating over the other.
Let's say I iterate over the vector code and change the vector data of my Test struct.
If I change the data in a function (change_data), I don't have a problem; but if I create a method to the Test struct with the same functionality, the compilation fails because I try to borrow the whole struct to the method.
Is there a way to create a method that changes data while iterating over code?

struct Test{
    code: Vec<u8>,
    data: Vec<u8>,
}
impl Test {
    #[allow(dead_code)]
    fn change_data(&mut self, index: usize, value: u8) {
        self.data[index]=value;
    }
}
#[allow(dead_code)]
fn change_data(data: &mut Vec<u8>, index: usize, value: u8) {
    data[index]=value;
}

fn main() {
    let mut test = Test{code: vec![1,2,3], data: vec![11,12,13]};
    for (i,c) in test.code.iter().enumerate() { // (Bad alternative: No error if I insert `.clone()` after code)
        //~ test.change_data(i,*c);    // error[E0502]: cannot borrow `test` as mutable because it is also borrowed as immutable
        change_data(&mut test.data, i, *c);
    }
    println!("{:?}", test.data);
}

(Playground) Line 20 works, but line 19 doesn't work if you uncomment it.
I appreciate any help I can get, thank you!

You need a single method to return references to both parts simultaneously, so something like this works:

impl Test {
    fn iter_both<'a>(&'a mut self)->impl Iterator<Item=(&'a u8, &'a mut u8)> {
        (&self.code).into_iter().zip((&mut self.data).into_iter())
    }
}

fn main() {
    let mut test = Test{code: vec![1,2,3], data: vec![11,12,13]};
    for (c,d) in test.iter_both() {
        *d = *c;
    }
    println!("{:?}", test.data);
}

(Playground)

This is known as “splitting borrows,” and you can read more details in the Rustonomicon.


Edit: Other options include things like providing an iterator of update functions:

impl Test {
    fn iter_update<'a>(&'a mut self)->impl Iterator<Item=(u8, impl FnMut(u8)+'a)> {
        let iter_code = (&self.code).into_iter().copied();
        let iter_data = (&mut self.data).into_iter().map(|pd| {
            move |x| *pd=2*x
        });
        iter_code.zip(iter_data)
    }
}
fn main() {
    let mut test = Test{code: vec![1,2,3], data: vec![11,12,13]};
    for (c, mut update) in test.iter_update() {
        update(c);
    }
    println!("{:?}", test.data);
}

(Playground)

Or accepting a closure that the struct applies to each item:

impl Test {
    fn update_data(&mut self, func: impl Fn(u8,u8)->u8) {
        let iter_code = (&self.code).into_iter().copied();
        let iter_data = (&mut self.data).into_iter();
        for (c, d) in iter_code.zip(iter_data) {
            *d = func(c,*d);
        }
    }
}
fn main() {
    let mut test = Test{code: vec![1,2,3], data: vec![11,12,13]};
    test.update_data(|c,_| c);
    println!("{:?}", test.data);
}

(Playground)

3 Likes

Thanks a lot!
I'll have to look more into your first two solutions, but I got it working for my original code with a closure like in your last solution.

But I've also found a solution more similar to my original code, which makes the code more flexible (I don't necessary want to change one element of data for every element of code.)
So I think it's best to split the data vector into another struct that gets its own method:

struct Test{
    code: Vec<u8>,
    data: Data,
}
struct Data {
    data: Vec<u8>,
}
impl Data {
    fn change_data(&mut self, index: usize, value: u8) {
        self.data[index] = value;
    }
}

fn main() {
    let mut test = Test{code: vec![1,2,3], data: Data{data: vec![11,12,13]}};
    for (i,c) in test.code.iter().enumerate() { 
        test.data.change_data(i,*c);
        if *c==3 && i > 0{                      // random condition to show that
            test.data.change_data(i-1, *c*3)    // I can change all elements of data here.
        }
    }
    println!("{:?}", test.data.data);
}

(Playground)

1 Like

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.