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]
}
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.
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: Rust Playground
edit:
And here is what I originally wanted, but fails:
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.
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.
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.
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.
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.
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.
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")
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?
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.
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:
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; }
}
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.