Amandasaurus
Amandasaurus

Reputation: 60699

Modifying one attribute of a struct while iterating over another attribute

I have a struct that has 2 Vecs. I want to be able to iterate over one while modifying the other one. Here's an example programme:

use std::slice;

struct S {
    a: Vec<i32>,
    b: Vec<i32>
}

impl S {
    fn a_iter<'a>(&'a self) -> slice::Iter<i32>  {
        self.a.iter()
    }
    fn a_push(&mut self, val: i32) {
        self.a.push(val);
    }
    fn b_push(&mut self, val: i32) {
        self.b.push(val);
    }
}

fn main() {
    let mut s = S { a: Vec::new(), b: Vec::new() };
    s.a_push(1);
    s.a_push(2);
    s.a_push(3);

    for a_val in s.a_iter() {
        s.b_push(a_val*2);
    }
}

But there is this compiler error:

$ rustc iterexample.rs 
iterexample.rs:28:9: 28:10 error: cannot borrow `s` as mutable because it is also borrowed as immutable
iterexample.rs:28         s.b_push(a_val*2);
                           ^
note: in expansion of for loop expansion
 iterexample.rs:26:5: 29:6 note: expansion site
iterexample.rs:26:18: 26:19 note: previous borrow of `s` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `s` until the borrow ends
iterexample.rs:26     for a_val in s.a_iter() {
                                   ^
note: in expansion of for loop expansion
iterexample.rs:26:5: 29:6 note: expansion site
iterexample.rs:29:6: 29:6 note: previous borrow ends here
iterexample.rs:26     for a_val in s.a_iter() {
iterexample.rs:27         println!("Looking at {}", a_val);
iterexample.rs:28         s.b_push(a_val*2);
iterexample.rs:29     }
                      ^
note: in expansion of for loop expansion
iterexample.rs:26:5: 29:6 note: expansion site
error: aborting due to previous error

I understand what the compiler is complaining about. I have borrowed self in the for loop, because I am still looping over it.

Conceptially there should be a way to do this though. I am only modifying s.b, and not modifying the thing I'm looping over (s.a). Is there anyway to write my programme to demostrate this separation, and allow this sort of programme to compile?

This is a simplified example of a larger programme, so I need to keep the general structure the same (a struct that has some things, one of which will be iterated over, and another that will be updated).

Upvotes: 3

Views: 411

Answers (4)

Amandasaurus
Amandasaurus

Reputation: 60699

I think I have "solved" this problem using macros. If I use the following code, it works:

use std::slice;

struct S {
    a: Vec<i32>,
    b: Vec<i32>
}

impl S {
    fn a_push(&mut self, val: i32) {
        self.a.push(val);
    }
}

macro_rules! a_iter {
    ($x: expr) => {
        { $x.a.iter() }
    }
}

macro_rules! b_push {
    ($x: expr, $val: expr) => {
        $x.b.push($val);
    }
}

fn main() {
    let mut s = S { a: Vec::new(), b: Vec::new() };
    s.a_push(1);
    s.a_push(2);
    s.a_push(3);

    let iter = a_iter!(s);

    for a_val in iter {
        println!("Looking at {}", a_val);
        b_push!(s, a_val*2);
    }
}

Here, I have moved the a_iter and b_push code into a macro. When the code is compiled, the macro will be expanded and it's as if we're not using an abstraction function(s). However for writing the code, that functionality is abstracted away.

I'm not sure if they is a good or bad idea.

Upvotes: 0

DK.
DK.

Reputation: 58975

The fundamental problem is that the borrow checker doesn't have enough information to prove your code is safe; it stops at function boundaries. You can get around this by writing a method that splits the reference such that the compiler does have the information it needs:

struct S {
    a: Vec<i32>,
    b: Vec<i32>
}

impl S {
    fn a_push(&mut self, val: i32) {
        self.a.push(val);
    }
    fn split_a_mut_b<'a>(&'a mut self) -> (&'a Vec<i32>, &'a mut Vec<i32>) {
        (&self.a, &mut self.b)
    }
}

fn main() {
    let mut s = S { a: Vec::new(), b: Vec::new() };
    s.a_push(1);
    s.a_push(2);
    s.a_push(3);

    let (a, b) = s.split_a_mut_b();

    for a_val in a.iter() {
        b.push(a_val*2);
    }
}

They key here is that within split_a_mut_b, the compiler can prove the two borrows do not overlap. Another pattern you can use, which lets you retain more of the original API, is to temporarily disassemble the value into mutable and immutable parts:

use std::slice;

#[derive(Debug)]
struct S {
    a: Vec<i32>,
    b: Vec<i32>
}

impl S {
    fn a_iter(&self) -> slice::Iter<i32>  {
        self.a.iter()
    }
    fn a_push(&mut self, val: i32) {
        self.a.push(val);
    }
    fn b_push(&mut self, val: i32) {
        self.b.push(val);
    }
    fn split_a_mut_b<F, R>(&mut self, f: F) -> R
    where F: FnOnce(&Self, &mut Self) -> R {
        use std::mem::swap;

        // Break off the mutable part(s) (or the immutable parts if there
        // are less of those).
        let mut temp = S { a: vec![], b: vec![] };
        swap(&mut self.b, &mut temp.b);

        // Call the closure.
        let result = f(self, &mut temp);

        // Glue the value back together.
        swap(&mut self.b, &mut temp.b);

        result
    }
}

fn main() {
    let mut s = S { a: Vec::new(), b: Vec::new() };
    s.a_push(1);
    s.a_push(2);
    s.a_push(3);

    s.split_a_mut_b(|imm, muta| {
        for a_val in imm.a_iter() {
            muta.b_push(a_val*2);
        }
    });

    println!("{:?}", s);
}

This is not terribly inefficient; this method introduces absolutely no heap activity; we're just shuffling pointers around.

Upvotes: 2

eulerdisk
eulerdisk

Reputation: 4709

You can remove the error if you use s.a.it instead of s.a_iter(). Your current solution doesn't work because the returned iterator from s.a_iter() keep a reference of s which have the same lifetime of s itself, and so until that reference is alive you cannot borrow as mutable something inside s. Specifically this happens because:

the compiler stops at the function call boundary when evaluating generic parameters

(the lifetime in your case)

Here there's a good answer which contains a complete explanation of a very similar problem: cannot borrow `self.x` as immutable because `*self` is also borrowed as mutable

Edit

A possible solution could be to bring the operation inside S instead of bring out the iterator from S. You can define in S a method like this:

fn foreach_in_a_push_to_b<F>(&mut self, func: F) where F : Fn(&i32) -> i32 {
    for a_val in self.a.iter() {
        self.b.push(func(a_val));
    }
}

and then

s.foreach_in_a_push_to_b(|&x| x * 2);

Upvotes: 4

mdup
mdup

Reputation: 8519

With raw pointers, you can alias the struct into a second variable -- Rust will consider them as two different variables and let you borrow the immutable part without complaining.

let s_alias = &s as *const S;
let a_iter = unsafe { (*s_alias).a_iter() };

for a_val in a_iter {
    s.b_push(a_val*2);
}

Playground

I welcome a second opinion on this, but I don't see how it could cause any memory safety issue, in this example at least.

Upvotes: 0

Related Questions