给定以下程序:
struct Data {
pub items: Vec<&'static str>,
}
trait Generator {
fn append(&mut self, s: &str) {
self.output().push_str(s);
}
fn data(&self) -> &Data;
fn generate_items(&mut self) {
for item in self.data().items.iter() {
match *item {
"foo" => self.append("it was foon"),
_ => self.append("it was something elsen"),
}
}
}
fn output(&mut self) -> &mut String;
}
struct MyGenerator<'a> {
data: &'a Data,
output: String,
}
impl<'a> MyGenerator<'a> {
fn generate(mut self) -> String {
self.generate_items();
self.output
}
}
impl<'a> Generator for MyGenerator<'a> {
fn data(&self) -> &Data {
self.data
}
fn output(&mut self) -> &mut String {
&mut self.output
}
}
fn main() {
let data = Data {
items: vec!["foo", "bar", "baz"],
};
let generator = MyGenerator {
data: &data,
output: String::new(),
};
let output = generator.generate();
println!("{}", output);
}
尝试编译它时会产生以下错误:
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:15:26
|
13 | for item in self.data().items.iter() {
| ---- - immutable borrow ends here
| |
| immutable borrow occurs here
14 | match *item {
15 | "foo" => self.append("it was foon"),
| ^^^^ mutable borrow occurs here
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:16:22
|
13 | for item in self.data().items.iter() {
| ---- - immutable borrow ends here
| |
| immutable borrow occurs here
...
16 | _ => self.append("it was something elsen"),
| ^^^^ mutable borrow occurs here
构建代码的正确方法是什么,以便在迭代不可变字段data
时可以写入可变字段output
?假设通过Generator
特征的间接寻址用于与其他结构共享类似的逻辑,因此从特征的默认方法实现访问MyStruct
的字段需要通过这样的访问器方法完成。
这是 Rust 中的一个常见问题;解决它的典型方法是替换舞蹈。这涉及使更多的数据和方法使用可变引用:
struct Data {
pub items: Vec<&'static str>,
}
trait Generator {
fn append(&mut self, s: &str) {
self.output().push_str(s);
}
fn data(&mut self) -> &mut Data;
fn generate_items(&mut self) {
// Take the data. The borrow on self ends after this statement.
let data = std::mem::replace(self.data(), Data { items: vec![] });
// Iterate over the local version. Now append can borrow all it wants.
for item in data.items.iter() {
match *item {
"foo" => self.append("it was foon"),
_ => self.append("it was something elsen"),
}
}
// Put the data back where it belongs.
std::mem::replace(self.data(), data);
}
fn output(&mut self) -> &mut String;
}
struct MyGenerator<'a> {
data: &'a mut Data,
output: String,
}
impl<'a> MyGenerator<'a> {
fn generate(mut self) -> String {
self.generate_items();
self.output
}
}
impl<'a> Generator for MyGenerator<'a> {
fn data(&mut self) -> &mut Data {
self.data
}
fn output(&mut self) -> &mut String {
&mut self.output
}
}
fn main() {
let mut data = Data {
items: vec!["foo", "bar", "baz"],
};
let generator = MyGenerator {
data: &mut data,
output: String::new(),
};
let output = generator.generate();
println!("{}", output);
}
要意识到的是,编译器抱怨是正确的。想象一下,如果调用output()
有改变返回值 data()
引用的事物的副作用,那么您在循环中使用的迭代器可能会失效。你的 trait 函数有一个隐式契约,它们不会做类似的事情,但没有办法检查这一点。因此,您唯一能做的就是通过将其取出来暂时完全控制数据。
当然,这种模式会破坏展开安全性;循环中的恐慌会使数据移出。
假设通过
Generator
特征的间接寻址用于与其他结构共享类似的逻辑,因此从特征的默认方法实现访问MyStruct
的字段需要通过这样的访问器方法完成。
那就不可能了。
编译器在直接看到不同字段时识别对这些字段的访问;它不会打破抽象边界来查看调用的函数内部。
已经有关于在方法上添加属性的讨论,以特别提及哪个字段由哪个方法访问:
- 编译器将强制方法不接触属性中未提及的任何字段
- 然后,编译器可以使用所述方法仅对字段子集进行操作的知识
然而。。。这适用于非虚拟方法。
对于特征,这变得更加复杂,因为特征没有字段,并且每个实现者可能具有不同的字段集!
那么现在呢?
您需要更改代码:
- 您可以将特征一分为二,并需要两个对象(一个用于迭代,一个用于变异)
- 您可以"隐藏"
append
方法的可变性,迫使用户使用内部可变性 - 。
RefCell
:
RefCell 使用 Rust 的生命周期来实现"动态借用",即 可以声明临时、排他性、可变访问权限的过程 内在价值。RefCells的借用在"运行时"被跟踪, 不像 Rust 的原生引用类型是完全跟踪的 静态,在编译时。因为 RefCell 借用是动态的,所以 可以尝试借用已经可变的值 借;发生这种情况时,会导致线程崩溃。
use std::cell::{RefCell, RefMut};
struct Data {
pub items: Vec<&'static str>,
}
trait Generator {
fn append(&self, s: &str) {
self.output().push_str(s);
}
fn data(&self) -> &Data;
fn generate_items(&self) {
for item in self.data().items.iter() {
match *item {
"foo" => self.append("it was foon"),
_ => self.append("it was something elsen"),
}
}
}
fn output(&self) -> RefMut<String>;
}
struct MyGenerator<'a> {
data: &'a Data,
output: RefCell<String>,
}
impl<'a> MyGenerator<'a> {
fn generate(self) -> String {
self.generate_items();
self.output.into_inner()
}
}
impl<'a> Generator for MyGenerator<'a> {
fn data(&self) -> &Data {
self.data
}
fn output(&self) -> RefMut<String> {
self.output.borrow_mut()
}
}
fn main() {
let data = Data {
items: vec!["foo", "bar", "baz"],
};
let generator = MyGenerator {
data: &data,
output: RefCell::new(String::new()),
};
let output = generator.generate();
println!("{}", output);
}
铁锈游乐场