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>会有所帮助,但无法构建它。请帮忙。
为了补充 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 替换t为c.out。