How is this a mut borrow?

I've been stuck trying to get this piece of code to work since last night and I'm just going in circles. I fix one compile problem and that opens up another one. I fix that one and in gives a new one. I fix the new one and I'm right back to where I started.

This is only one of the problems I have, but I'm trying to iterate through a vector on an object in my struct. Rust sees this is a mut borrow, but I completely don't see how.

Here's a link to the Rust playground where I got this error to happen. Specifically, line 42 is the closest to where I'm struggling in my program. You can ignore all the extra code. I was originally trying to reproduce a different error, but could only get this one to work.

You can't push to a Vec (SomeObject::Increment) while an iterator that references the Vec (for i in ids) exists, as the push could cause a reallocation that causes all the references to dangle.

(Rust makes this impossible at a more fundamental level by making &mut exclusive, so you can't even call Increment; the details about why it would be bad for push are incidental, but hopefully illustrative.)

It's hard to tell what you're actually trying to do from the example (due to e.g. the infinite loop, which incidentally hides some borrow errors as it means the for loop body only executes once). So it's hard to give further advice.

1 Like

How is what a mutable borrow?


One issue is obvious, however: you are trying to call mutating methods through immutable references. In addition, you are trying to call mutating methods while immutably borrowing the same object, which is wrong, and which is exactly what Rust is designed to prvent.

One important aspect of Rust’s concept of mutability is that it applies to an object itself and to all things it contains (owns). In your case,

pub struct SomeObject {
    id_list: Vec<u64>,
    counter: u64
}

pub struct SomeBiggerObject {
    a: SomeObject
}

impl SomeBiggerObject {
    pub fn Run(&self) {
        // …
    }
}

in the Run method, the &self means that your SomeBiggerObject is passed to the method by immutable reference. This means you cannot mutate the SomeBiggerObject nor the SomeObject in self.a, nor can you mutate the Vec<u64> in self.a.id_list or the u64 in self.a.counter. You cannot mutate them directly, or by calling a method such as Increment or push (either being a &mut self-method for their respective type).

For line 42 in particular, the error message is not even concerned with mutability per say but moving / ownership, by the way:

error[E0507]: cannot move out of `self.a.id_list` which is behind a shared reference
  --> src/main.rs:42:19
   |
42 |         let ids = self.a.id_list;
   |                   ^^^^^^^^^^^^^^ move occurs because `self.a.id_list` has type `Vec<u64>`, which does not implement the `Copy` trait
   |
help: consider borrowing here
   |
42 |         let ids = &self.a.id_list;
   |                   +

as for fixing the problem… well, you can make ids be a reference to id_list to avoid the ownership claim[1] (as it stands, ids is a variable of type Vec<u64> and “wants” to own this vector). Whether or not you’d need this reference to be mutable depends on your use-case, either would support for loops though, but for mutability, you might need/want to change the method signature for Run to take &mut self, too. Another fix, especially useful for beginners, could be to clone the data (let ids = self.a.id_list.clone();). This has the advantage of avoiding all possible borrowing errors/problems from popping up from usages of ids, and the disadvantage of being somewhat “inefficient”, since it copies all the data, as well as giving up any “object identity” you might be assuming, i.e. subsequent mutations to ids or to self.a.id_list would not be applies to the other, respectively.


Another important aspect of Rust’s concept of mutability of course is that it’s exclusive access, so “adding mut everwhere” can quickly get you into new problems. Avoiding the experience of having this as a limiting factor admittedly requires some experience and getting-used-to, or tinkering-around; feel free to also come back with a more complete / reasonable / actual (toy) code where the whole code does something coherent, but you cannot get it to work, for advice on how to restructure the access (mutable vs. immutable references vs. potential extra alternatives), and possibly restructure even the struct definitions themselves in order to make them more workable.


  1. this is also the compiler suggestion here… of course applying this doesn’t immediately make the rest of the code compile, either ↩︎

1 Like

Wow. Thanks for all the information. I definitely need to revisit my understanding of mutability and borrowing. I'm going to deconstruct my code. If that doesn't get me my answer, I'll come back with a better example.

I got a reproducible example. That didn't take as long as I thought. Taking some time off from the problem must have helped.

The code involves scheduling and timelines. The timeline is a Vector of timeslots which each has its own Vec of IDs. I want to iterate through the Vec of IDs on the first timeslot (the present moment in time), then updated later parts of the timeline based on what I find.

As you can see, the problem is on line 33. My guess is that Rust sees line 33 as having the potential to disrupt current_ids. And, I suppose that's the case, but how the heck else am I supposed to do this? I need to refer to the my_sched object somehow. Am I just going about this in a fundamentally non-Rustic way?

Just as another remark for refining your understanding of borrow-checking, another important aspect is that function boundaries are important and reasoning is local. I.e. what I mean is that the borrow checker determines what line 33 does only by the function signature pub fn add_new_timeslot(&mut self), and with a &mut self access to the whole Schedule, calling this method could indeed “disrupt current_ids”. (Sometimes inlining functions can thus help borrow-checking, but in this case inlining would be useless, too, as the .push method itself also is a &mut self method on the Vec<TimeSlot>, so this method’s signature grants it enough access to “disrupt current_ids”, too.)

There are a few workarounds I can think of.


One I’ve already mentioned before is cloning the vector.

-   let current_ids = &my_sched.get_present().id_list;
+   let current_ids = my_sched.get_present().id_list.clone();

    for i in current_ids {
-       if *i == 2 as u64 {
+       if i == 2 as u64 {
            my_sched.add_new_timeslot();
        }
    }

This approach is very easy to do. The code structure works as-is, cloning removes the dependence of current_ids on a borrow of my_sched. The downside is that it does mean allocating a copy of the whole Vec<u64> in question. Not too bad, given there’s a loop over it anyways, so the time overhead is probably negligible, but it takes some extra memory.


Another approach is to move the mutations after the access to current_ids. You can use the loop to determine what needs to be done, then do it afterwards. In this case, all information necessary would be how often to call add_new_timeslot.

    let current_ids = &my_sched.get_present().id_list;

+   let mut n: usize = 0;
    for i in current_ids {
        if *i == 2 as u64 {
-           my_sched.add_new_timeslot();
+           n += 1;
        }
    }
+   for _ in 0..n {
+       my_sched.add_new_timeslot();
+   }

Another approach would be to hold some form of run-time “lock” onto the id_list. Such approaches can often involve primitives such as Cell or RefCell or Mutex this page in the book, but in this case, a similar idea can be as straightforward as just moving the id_list out of the present, leaving a dummy (empty vec) in place before the loop, and moving it back afterwards. We have convenient API for this (i.e. for the moving out leaving dummy value part)

+use std::mem;

-   let current_ids = &my_sched.get_present().id_list;
+   let current_ids = mem::take(&mut my_sched.get_present().id_list);

-   for i in current_ids {
+   for i in &current_ids {
        if *i == 2 as u64 {
            my_sched.add_new_timeslot();
        }
    }

+   my_sched.get_present().id_list = current_ids;

For types without good dummy values, or if you want to make sure even better that one never possibly mixes up the empty list and the dummy value, wrapping the relevant field in an Option is always also an option, and it would come with an even more convenient way to the “moving out leaving dummy value” operation.

3 Likes

Just some additional comments on idiomatic Rust...

One result of this is that getters and setters are less common in Rust than some other languages, and when they do exist, they're less often used within the same crate as the data structure. It doesn't necessarily help in this case, but it avoids some types of interprocedural conflict.

There are some other idiomatic patterns you could take advantage of, such as deriving Default and Debug.

Then you can do things like

    // Instead of a `println` loop
    dbg!(&my_ids);

    pub fn add_new_timeslot(&mut self) {
        self.timeline.push(TimeSlot::default());
        //                 ^^^^^^^^^^^^^^^^^^^
    }

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.