对已经可变引用的对象保持读写控制的最佳“Rust”方法是什么?

los*_*cos 0 rust borrow-checker

假设我想测试一个 struct ( ) 的行为,Consumer该 struct ( ) 采用对另一个 struct ( ) 的可变引用Target。测试其正确性的唯一方法是直接读写访问Target. 我们保证这里没有多线程。

我想到了类似下面的内容,尽管由于明显违反借用规则而无法编译:t仍然可变借用,而我们稍后想再次借用它。

use std::io::Write;

struct Consumer<'a, W: Write> {
    out: &'a mut W
}

impl<'a, W: Write> Consumer<'a, W> {
    pub fn consume(&mut self, data: &[u8]) {
        // do something else
        self.out.write(data).unwrap();
    }
}

struct Target {
    buf: Vec<u8>
}

impl Target {
    pub fn drain(&mut self) {
        self.buf.clear();
    }
}

impl Write for Target {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        self.buf.extend_from_slice(buf);
        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

mod tests {
    use crate::{Target, Consumer};

    #[test]
    fn test_that_consumes_ok() {
        let mut t = Target{ buf: Vec::new() };
        let mut c = Consumer{ out: &mut t };
        c.consume(b"hello");
        c.consume(b"world");

        // error in this line:
        assert_eq!(t.buf.as_slice(), b"helloworld");

        // also error:
        t.drain();

        c.consume(b"new");
        assert_eq!(t.buf.as_slice(), b"new");
    }
}


fn main() {}
Run Code Online (Sandbox Code Playgroud)

因此,问题不在于这种测试方法本身(这是有问题的),而在于如何在没有防弹方式的Arc<Mutex>情况下提供对多个位置的可变引用。直觉上我明白类似的东西Rc<RefCell>会有所帮助,但无法构建它。请帮忙。

Mas*_*inn 5

为了补充 cafce25 的评论,并且要明确的是,仅仅因为没有多线程并不意味着 R|W 没有不安全性:因为编译器从外部知道的所有内容都会Consumer保留对outs 内容的引用,而写入可能会使其无效。一个经典的例子是这样的

fn foo(v: &mut Vec<u8>) {
    let e = &v[0];
    v.push(5);
}
Run Code Online (Sandbox Code Playgroud)

在 C++ 中,这可能会导致 UB,因为push可能需要调整向量的大小,这会释放旧缓冲区,从而使引用无效e(如果清除向量,情况会更加明显,但不太可能被视为无害)。另一个经典的问题是“迭代器失效”,您在迭代集合时更新集合的内容,这通常具有相同的问题(正如名称所示,迭代器本身可能会变得无效),甚至会影响内存安全语言:其中一些开始返回无意义的内容,其他人会防范它并触发运行时错误,例如ConcurrentModificationExceptionjava中的可怕的不需要任何线程:

如果单个线程发出一系列违反对象约定的方法调用,则该对象可能会抛出此异常。例如,如果线程在使用快速失败迭代器迭代集合时直接修改集合,则迭代器将抛出此异常。

不过,您有多种方法可以处理该问题:

  • 执行此操作的“公共”方法是使Consumeryield成为对其包含的子引用target。这是一个足够常见的模式,您可以看到它BufWriter::get_mut允许获取底层编写器的句柄。

  • 但是因为它Consumer是完全开放的(因为您可以使用结构文字构造它),所以您实际上可以在这里访问它:

    let t = &mut *c.out;
    
    Run Code Online (Sandbox Code Playgroud)

    会工作得很好。然而,在再次调用之前,必须释放该子借用Consumer::consume,因此您还需要在底部及其c.consume(b"new");断言之间添加该子借用的副本。

    或者,您可以将 every 替换tc.out