Reputation: 2642
My code mutably borrows a reference to Application
, but does not seem to understand that the borrow should only last for the duration of the function, as it complains about a second mutable borrow. How can I say that I only want the borrow to last for the duration of the function call?
use std::error::Error;
pub trait Plugin {
fn initialize(&mut self, application: &mut Application) -> Result<(), Box<dyn Error>>;
}
pub struct Application {
plugins: Vec<Box<dyn Plugin>>,
}
impl Application {
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
for plugin in self.plugins.iter_mut() {
plugin.initialize(self);
}
Ok(())
}
}
| for plugin in self.plugins.iter_mut() {
| -----------------------
| |
| first mutable borrow occurs here
| first borrow later used here
| plugin.initialize(self);
| ^^^^ second mutable borrow occurs here
Upvotes: 0
Views: 227
Reputation: 169328
but does not seem to understand that the borrow should only last for the duration of the function
But the borrow is extended because the iterator returned by self.plugins.iter_mut()
continues to hold an exclusive borrow against self
. This is necessary, otherwise you could call self.plugins.clear()
and invalidate your iterator. self
is considered exclusively borrowed for as long as the iterator continues to exist.
In this case, one way around the issue would be to take self.plugins
and put it back after the loop. Since Vec
stores elements on the heap, this has O(1) time complexity; a pointer is just being copied around.
Note also that you forgot to handle the Result
from plugin.initialize()
. Since we are taking self.plugins
, we need to be careful to put it back when we're done; simply using the ?
operator would result in self.plugins
being empty if an error occurs.
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
let mut r = Ok(());
let mut plugins = std::mem::take(&mut self.plugins);
for plugin in plugins.iter_mut() {
r = plugin.initialize(self);
if r.is_err() { break; }
}
self.plugins = plugins;
r
}
If this is a pattern that comes up a lot in your code, you can encapsulate it behind a private method. This will also simplify error handling:
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
self.with_plugins(|s, plugins| {
for plugin in plugins.iter_mut() {
plugin.initialize(s)?;
}
Ok(())
})
}
fn with_plugins<F, T, E>(&mut self, f: F) -> Result<T, E>
where F: FnOnce(&mut Self, &mut Vec<Box<dyn Plugin>>) -> Result<T, E>
{
let mut plugins = std::mem::take(&mut self.plugins);
let r = f(self, &mut plugins);
self.plugins = plugins;
r
}
If you wanted to be extra safe, you could even assert that self.plugins
is empty before putting plugins
back; if it's not, that indicates that something tried to mutate it when they shouldn't have.
fn with_plugins<F, T, E>(&mut self, f: F) -> Result<T, E>
where F: FnOnce(&mut Self, &mut Vec<Box<dyn Plugin>>) -> Result<T, E>
{
let mut plugins = std::mem::take(&mut self.plugins);
let r = f(self, &mut plugins);
assert!(self.plugins.is_empty());
self.plugins = plugins;
r
}
Upvotes: 2