如何在可变借用的情况下安全地使用对象?

时间:2016-12-21 15:16:03

标签: rust immutability borrow-checker

我有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();
    }
}

也就是说,我有一些经理调用回调做一些工作。我希望回调为Worker::do_work(),该函数会更新Worker的成员,因此需要&mut self。但是,一旦我将worker.do_work()传递给经理,就意味着worker被可怜地借用,所以我再也不能使用它了。

我想再次使用它来检查进度,并可能改变它的行为。我可以使用原子操作和互斥体等来尝试确保这样做是安全的,但是如何告诉Rust允许这样做而不会出现cannot borrow X as immutable because it is also borrowed as mutable错误?

我猜这与CellRefCell有关,但我无法解决这个问题。

1 个答案:

答案 0 :(得分:0)

这是我将要使用的更简单的例子:

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

添加RefCell允许它编译:

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

现在,闭包借用了RefCell<Worker>的不可变引用,并检查从编译时到运行时的独占可变借位。

当然,解决问题不需要RefCell ,但避免RefCell确实意味着您必须从不同的方向查看问题。一种解决方案是保留Worker,而不是保留Manager。然后根据需要借回来:

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