Rick de Water
Rick de Water

Reputation: 2642

How to make a borrow only last for the duration of a function call

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?

Code

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(())
    }
}

Output

|         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

Answers (1)

cdhowie
cdhowie

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

Related Questions