How do I safely use an object while it is mutably borrowed?

136 views Asked by At

I have this code:

use std::sync::atomic::{AtomicIsize, Ordering};

#[derive(Default)]
pub struct Worker {
    work: Vec<u32>,
    progress: AtomicIsize,
}

impl Worker {
    fn do_work(&mut self) {
        self.work.push(0u32);
        self.progress.store(self.progress.load(Ordering::SeqCst) + 1, Ordering::SeqCst);
    }
    fn get_progress(&self) -> isize {
        self.progress.load(Ordering::SeqCst)
    }
}

pub struct Manager<CB: FnMut()> {
    cb: CB
}

impl<CB: FnMut()> Manager<CB> {
    fn do_a_bit_more_work(&mut self) {
        (self.cb)();
    }
}

fn main() {
    let mut worker = Worker::default();

    let mut manager = Manager {
        cb: || worker.do_work()
    };

    while worker.get_progress() < 100 {
        manager.do_a_bit_more_work();
    }
}

That is, I have some manager that calls a callback to do some work. I want the callback to be Worker::do_work() and that function updates the members of Worker so it needs &mut self. However once I pass worker.do_work() to the manager it means worker is mutably borrowed so I can never use it again.

I want to use it again to check progress, and maybe change its behaviour. I can use atomic operations and mutexes and so on to try to make sure it is safe to do so, but how can I tell Rust to allow this without getting the cannot borrow X as immutable because it is also borrowed as mutable error?

I'm guessing it is something to do with Cell or RefCell but I can't work it out.

1

There are 1 answers

0
Shepmaster On

Here's the simpler example I'm going to use:

struct Manager<F> {
    cb: F,
}
impl<F> Manager<F>
    where F: FnMut()
{
    fn do_a_bit_more_work(&mut self) { (self.cb)() }
}

struct Worker;
impl Worker {
    fn do_work(&mut self) {}
    fn get_progress(&self) -> u8 { 100 }
}

fn main() {
    let mut worker = Worker;

    let mut manager = Manager {
        cb: || worker.do_work()
    };

    while worker.get_progress() < 100 {
        manager.do_a_bit_more_work();
    }
}

Adding RefCell allows it to compile:

use std::cell::RefCell;

fn main() {
    let worker = RefCell::new(Worker);

    let mut manager = Manager {
        cb: || worker.borrow_mut().do_work()
    };

    while worker.borrow().get_progress() < 100 {
        manager.do_a_bit_more_work();
    }
}

Now the closure borrows an immutable reference of the RefCell<Worker> and checking for an exclusive mutable borrow moves from compile time to runtime.

Of course, RefCell isn't required to solve the problem, but avoiding RefCell does mean you have to look at the problem from a different direction. One solution is instead of keeping the Worker, give it to the Manager. Then borrow it back as needed:

trait DoWork {
    fn do_work(&mut self);
}

struct Manager<T> {
    work: T,
}

impl<T> Manager<T>
    where T: DoWork
{
    fn do_a_bit_more_work(&mut self) {
        self.work.do_work()
    }

    fn inspect<F, U>(&self, mut f: F) -> U
        where F: FnMut(&T) -> U
    {
        f(&self.work)
    }

    // Optionally
    // fn inspect_mut<F, U>(&mut self, mut f: F) -> U
    //    where F: FnMut(&mut T) -> U
    // {
    //     f(&mut self.work)
    // }

    fn into_inner(self) -> T {
        self.work
    }
}

struct Worker;
impl Worker {
    fn get_progress(&self) -> u8 {
        100
    }
}

impl DoWork for Worker {
    fn do_work(&mut self) {}
}

fn main() {
    let worker = Worker;

    let mut manager = Manager { work: worker };

    while manager.inspect(|w| w.get_progress()) < 100 {
        manager.do_a_bit_more_work();
    }

    let worker = manager.into_inner();
}