Borrow Error within Loop

Given:

  • A struct Val that cannot be copied or cloned.
  • A struct CurrentVals that contains Val objects.
  • CurrentVals::perform_task takes a mutable reference to self and mutates Self (in unspecified manner but with the guarantee that CurrentVals::vals will NOT be mutated).
#![allow(unused)]

struct Val {
    // ...
}

struct CurrentVals {
    vals: Vec<Val>,
    // ...
}

impl CurrentVals {
    fn perform_task(&mut self, val: &Val) {
        // ...
    }

    pub fn new() -> Self {
        // vals is filled in an unspecified manner
        Self { vals: vec![] }
    }
}

pub fn main() {
    let mut current_vals = CurrentVals::new();

    for val in &current_vals.vals {
        current_vals.perform_task(val)
    }
}

When I try to execute the program, Rust shows the error:

   |     for val in &current_vals.vals {
   |                ------------------
   |                |
   |                immutable borrow occurs here
   |                immutable borrow later used here
   |         current_vals.perform_task(val)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

While I know why this error happens, I would like to know how I can solve the issue.

If you don't need access to the Vec of Vals inside perform_task, you can temporarily remove them from the parent struct:

    let vals = std::mem::take(&mut current_vals.vals);
    for val in &vals {
        current_vals.perform_task(val)
    }
    current_vals.vals = vals;

Otherwise you're looking at the other workarounds for the general problem of interprocedural conflicts, like factoring your struct, view types, etc.

1 Like

Well, then don't take the entirety of &mut self. Mutably borrow only those parts/fields which actually need to be mutated. If you move the splitting into the same function, the borrow checker can reason about it just fine:

pub fn main() {
    let mut current_vals = CurrentVals::new();

    for val in &current_vals.vals {
        CurrentVals::perform_task(&mut current_vals.other_thing, val);
    }
}

The issue is, it is yet unknown how many parts/fields will actually be mutated. It can be 5, 10, 20 etc. I cannot possibly update the function all the time, more so given the fact that it will be a part of the public API.

This is a code smell. I highly suggest re-thinking the design if you have a function that mutates 20 fields of a type.

Thanks for the solution. It'll work, but it makes me feel uneasy about Rust's inner mechanics. To the (biased) human eye, it looks like it is a simple task that should not take too much of thinking, but Rust's guarantee of protection backfires here.

Anyways, this solution works just fine! Thanks a lot!

Quinedot's solution is also easy and doesn't require too much thinking. Precisely what is it that makes you feel uneasy about it?

I am not a pro in Rust. And since I come from C/Python background, I had no idea I'd trip over a simple programming problem. But since I'm still learning, what looks difficult/arcane right now will become the norm.

If you want an explanation of why it's not allowed...

  • Since perform_task has &mut self, it could do something like
    • self.vals = Vec::new();
  • This would immediately drop (deallocate) all the Vals in the Vec
  • So your val reference you passed in would dangle
  • I.e. UAF (use after free), exactly one of the bugs safe Rust guarantees can't happen
1 Like

I already had suspicions about this issue, that's why I stated that CurrentVals::vals will NOT be mutated in any circumstances.

It's not us you need to convince, it's the compiler. There is absolutely nothing wrong, as far as the compiler knows, with some future maintainer coming along one day and changing the body of perform_task to do exactly that. The easiest way to tell the compiler that a function doesn't mutate something, is not to pass that thing to the function at all.

3 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.