Cannot borrow `*self` as mutable more than once at a time - how would idomatic Rust code looks like?


#1

Hi there. I’m somewhat new to Rust but I like it so far. But I frequently stumple over the same problem:

cannot borrow *self as mutable more than once at a time

Which is - honestly - a bit annoying. What I want:
Check, if there is an element in an internal Vector. If so, I want to access it, increase the current cursor-position or index and do something with the element. That looks like this:

if self.is_finished() && bundle.allow_next_iteration() {
    self.reset(); // Set index to 0
}

if self.has_next() {
   return self.dequeue().execute(bundle, self); // dequeue get's the current element and increases the index
}

That - obviously - does not work, since I’m borrowing self as mutable twice. But how can I avoid it? Normally I would just give up and work with D or C++, but Rust is so nice so I want to understand how idiomatic Rust code would look like. If necessary, I can provide a full example, which should be ~150 lines of code.
The four important functions are those:

fn dequeue(&mut self) -> &Box<Middleware<B>>
{
    let index = self.index;
    self.index += 1;
    
    &self.middlewares[index]
}

fn has_next(&self) -> bool
{
    self.middlewares.len() > self.index
}

fn is_finished(&self) -> bool
{
    !self.has_next()
}

fn reset(&mut self)
{
    self.index = 0;
}

#2
fn dequeue(&mut self) -> &Box<Middleware<B>>{
   let index = self.index; 
   self.index += 1; 
   &self.middlewares[index]
}

The issue is that returning a &Box<...> extends the mutable borrow of self. Is there a reason you’re only logically dequeuing? For example, why not return the Box<...> to the caller, which physically removes it from your vector?

Unless you switch to interior mutability, you won’t be able to borrow &mut self if you continue returning &Box<...> and then passing self to the execute() method.

Can you expand a bit on what you’re trying to do? Maybe a distilled example on the playground would help, or the ~150 lines of code is fine too.


#3

Thanks for the fast reply. Here is what I got so far, it’s using a VecDeque, but what I original wanted was a Vector, so that I can reset and loop again. Here is the link to the playground: https://play.rust-lang.org/?gist=77e89ccb6a4d5db8301d418c234c94c5&version=stable

edit:
And here is what I originally wanted, but fails:
https://play.rust-lang.org/?gist=8e3c0045dabfa856b61fe898d2db2034&version=stable


#4

Are you always going to walk over the entire Vec when calling execute? The playground code does that, in a slightly roundabout way, but curious if that’s the real usage scenario or not.


#5

Each Middlewar can decide, if the next one is executed by calling or not calling next.execute(…). But normally the working process is splittet in single units which process the data one after another. If one fails it could stop the execution of the next one. The bundle define how often the iterations/loops takes place with the allow_next_iteration method. We’ve been using this pattern often in PHP, Java, C++ and D.


#6

Is the sole purpose of it execute(bundle, self) taking self to decide at the end whether to return a result or iterate to the next? (i.e. it isn’t reading anything else.)

If so one approach is to drop the addition of self and return an enum that either has the result or is maker to continue.


#7

self is the Queue, but also the Delegate and therefore the next element, which can be executed. How would your solution look like?


#8

In your example, the Queue is always the next delegate - that’s what I meant by the playground example iterating the whole list in a roundabout fashion.

So you can split responsibilities: an executor (and owner) of middleware values (your queue) and middleware. Queue iterates over the middleware, executes it and middleware returns an indicator whether iteration should proceed.


#9

I could do that. But what’s the real deal behind “Cannot borrow *self as mutable more than once at a time”? In this case I don’t see the benefit.


#10

It prevents things like iterator invalidation, as a classic example. You should think of immutable/mutable borrow as a singlethreaded read/write lock. You can have multiple immutable borrows at the same time (no harm - nothing can mutate the data) or you can have a single mutable borrow (you get exclusive access and can do whatever you want, including mutation). This is one of the cornerstones of Rust, and it will often steer you towards a certain design. In the end, I think you’ll mostly find that it leads to cleaner code and dataflow.


#11

For a more elaborate discussion of the issues that can emerge out of shared access to mutable data, you can read this classic from Manish: https://manishearth.github.io/blog/2015/05/17/the-problem-with-shared-mutability/ .


#12

There’s also something to be said about the strong aliasing guarantees allowing for better optimizations. If compiler knows that it has an immutable reference then it can be certain that values won’t be changed underneath it and it can more aggressively optimize the code.

Similarly, it’s better for the developer - you can tell at a glance whether you have mutable/immutable access to something or not, and the mutable stuff flows “linearly” (i.e. you pass it on to something, aka reborrow, but it’s to a single point at a time). This makes it easier to reason locally about state changes, and not be paranoid about who might be changing what.


#13

So there is no way to implement this common pattern which works in so many other languages with no problems?

edit:
I’ve tried another attempt, but now I’m getting this:

cannot borrow *self as mutable because self.middlewares is also borrowed as immutable

I’m much more confused now.

It’s important, that the current Middleware knows the next one. In this simple example it may see a bit quirky, but it’s necessary.


#14

From the desrciption so far it sounds like the whole process could be written as

self.middlewares.iter().map(|m| m.execute()).collect::<Result<Vec<_>>>()

which would run through the middlewares, stopping as soon as one returns Err. (it will also produce a Vec; I kind of wish we had some sort of "Iterator /dev/null")


#15

I would argue that for loops and the new for_each iterator methods are quite effective as far as Iterator /dev/null go.

On the other hand, this still does not address the OP’s latest request. @Dgame, can you provide an example of a situation where you would really want middleware N to have access to middleware N+1?


#16

Not in Rust I fear. But I can tell you. In some occasions we have to give some data from Middleware A to Middleware B. Originally that was not planned, but reality is always against theory.

We want to change this fact and use the Bundle as, well, Data-Bundle, but that is work in progress, therefore we need a working solution which can be looped.


#17

Is that data from Middleware A to Middleware B solely about feeling the latter’s execute() method? If so, you could make Middleware::execute() return something like this:

struct MiddlewareOutcome {
    bundle: Box<B>,
    runNextMiddleware: bool,
}

The executor would check the flag. If it’s false, the processing is complete and the bundle is returned. If it’s true, the next middleware is invoked with the bundle as a parameter. In other words, you would have something like…

let mut bundle = /* ... initial value ... */;
for middleware in self.middlewares {
    let outcome = middleware.execute(bundle);
    bundle = outcome.bundle;
    if !outcome.runNextMiddleware { break; }
}

#18

Assuming .execute() returns Result<()>, I think this will soon be .collect::<Result<()>>():

https://github.com/rust-lang/rust/pull/45379

(I also want to add a self.middlewares.iter().try_foreach(|m| m.execute()); to go with the new try_fold methods.)


#19

If you can’t (or don’t want to) make it work with static borrow checking, then you can use interior mutability; use RefCell to borrow mutably dynamically and remove &mut‘ in favor of&‘ for places where you’re currently trying to borrow something mutably that’s already borrowed.

Keep in mind that if you violate the borrow rules dynamically, the RefCell will panic.


#20

How would that looks like? Can you give me a short example?