【问题标题】:How do I safely use an object while it is mutably borrowed?如何在可变借用对象时安全地使用它?
【发布时间】:2016-12-21 15:16:03
【问题描述】:

我有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 的成员,因此它需要&amp;mut self。但是,一旦我将worker.do_work() 传递给经理,这意味着worker 是可变借用的,所以我再也不能使用它了。

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

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

【问题讨论】:

  • 请展示您使用CellRefCell 完成的工作。 std::cell 文档中有很多关于这些类型的文档。
  • 对您提出的问题的直接回答(我如何告诉 Rust 可以在已经可变借用时将 self 作为不可变借用?)是:you不要。这样做本质上是不安全的。
  • @ker:我知道CellRefCell 的想法,我只是不确定如何最好地将它们应用于这种情况。文档对此并没有真正的帮助。
  • 一个(非)可编译的最小示例,我相信它证明了 OP 得到的确切错误:playground。尽管 OP 的代码存在明显的缺点,但我也很好奇如何构造事物以使 while 循环成为可能。
  • 如果示例代码避免在其他可变数据结构中无偿使用原子类型,那么它的混淆就会减少。请注意,x.store(x.load() + 1) 是一种反模式,它违背了 any 语言中原子的目的。

标签: rust immutability borrow-checker


【解决方案1】:

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

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&lt;Worker&gt; 的不可变引用,并检查从编译时到运行时的排他可变借用。

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

【讨论】:

    猜你喜欢
    • 2014-06-28
    • 1970-01-01
    • 1970-01-01
    • 2021-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-17
    相关资源
    最近更新 更多