你能告诉我如何改变这个设计以使其编译吗?
我想要
acquire
对集合中某个元素的可变引用,写入它并且(一旦我不再需要它)release
它。
use std::{collections::VecDeque, str::FromStr};
struct Elem {
value: String,
flag: bool,
}
impl Default for Elem {
fn default() -> Self {
Self { value: Default::default(), flag: false }
}
}
struct ProdCons {
buffer: VecDeque<Elem>,
foo: bool
}
impl ProdCons {
fn acquire(&mut self) -> &mut Elem {
self.foo = true;
self.buffer.push_back(Default::default());
let elem = self.buffer.back_mut().unwrap();
elem.flag = true;
self.foo = false;
return elem
}
fn release(&mut self, elem: &mut Elem) {
self.foo = true;
elem.flag = false;
self.foo = false;
}
}
fn main() {
let mut pc = ProdCons{ buffer: VecDeque::new(), foo: false };
let elem = pc.acquire();
elem.value = String::from_str("test").unwrap();
pc.release(elem);
}
但是,目前代码无法编译:
error[E0499]: cannot borrow `pc` as mutable more than once at a time
--> src\main.rs:50:5
|
47 | let elem = pc.acquire();
| ------------ first mutable borrow occurs here
...
50 | pc.release(elem);
| ^^^^^^^^^^^----^
| | |
| | first borrow later used here
| second mutable borrow occurs here
我的理解是,这是因为
pc
已经被可变地借用了,因为Rust不能保证改变它不会改变elem
。
另一方面,我知道
release
功能是我将不再需要elem
的地方。我该如何用 Rust 来表达这一点?
我的理解是,这是因为
已经被可变地借用了,因为Rust不能保证改变它不会改变pc
。elem
是的,但是编译器可以执行此分析。事实并非如此,因为如果确实如此,那么您无法通过查看文档或函数签名来判断调用该函数所施加的要求是什么 - 以及编译的程序是否取决于实现细节比如函数是否访问某个字段。
所以,相反:
ProdCons::acquire()
采用&mut ProdCons
,这意味着整个ProdCons
可能会发生突变。
解决方案是移动执行标志更新的位置 - 不要在单独的
release
函数中执行它们,而是在 acquire()
创建的“守卫”中执行它们。然后,为了使代码更惯用且不易出错,请完全消除 release()
并在 drop()
中完成工作。
执行此操作的新代码:
impl ProdCons {
fn acquire(&mut self) -> Guard<'_> {
...
Guard {
elem,
foo: &mut self.foo,
}
}
}
struct Guard<'a> {
elem: &'a mut Elem,
foo: &'a mut bool,
}
impl Deref for Guard<'_> {
type Target = Elem;
fn deref(&self) -> &Self::Target {
self.elem
}
}
impl DerefMut for Guard<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.elem
}
}
impl Drop for Guard<'_> {
fn drop(&mut self) {
*self.foo = true;
self.elem.flag = false;
*self.foo = false;
}
}
在其他情况下,例如如果您想在释放/删除之前获取多个元素,您可能需要达到内部可变性,在这种情况下,您可以在例如
typed-arena
这是追加然后变异的方式与当前代码相同,但允许在其他可变引用仍然存在时追加。
使用上述代码完成程序:
use std::ops::{Deref, DerefMut};
use std::{collections::VecDeque, str::FromStr};
struct Elem {
value: String,
flag: bool,
}
impl Default for Elem {
fn default() -> Self {
Self { value: Default::default(), flag: false }
}
}
struct ProdCons {
buffer: VecDeque<Elem>,
foo: bool
}
impl ProdCons {
fn acquire(&mut self) -> Guard<'_> {
self.foo = true;
self.buffer.push_back(Default::default());
let elem = self.buffer.back_mut().unwrap();
elem.flag = true;
self.foo = false;
Guard {
elem,
foo: &mut self.foo,
}
}
}
struct Guard<'a> {
elem: &'a mut Elem,
foo: &'a mut bool,
}
impl Deref for Guard<'_> {
type Target = Elem;
fn deref(&self) -> &Self::Target {
self.elem
}
}
impl DerefMut for Guard<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.elem
}
}
impl Drop for Guard<'_> {
fn drop(&mut self) {
*self.foo = true;
self.elem.flag = false;
*self.foo = false;
}
}
fn main() {
let mut pc = ProdCons{ buffer: VecDeque::new(), foo: false };
{
let mut elem = pc.acquire();
elem.value = String::from_str("test").unwrap();
}
assert_eq!(pc.foo, false);
}