鉴于以下计划:
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 foo\n"),
_ => self.append("it was something else\n"),
}
}
}
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);
}
Run Code Online (Sandbox Code Playgroud)
尝试编译时会产生以下错误:
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 foo\n"),
| ^^^^ 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 else\n"),
| ^^^^ mutable borrow occurs here
Run Code Online (Sandbox Code Playgroud)
构造代码的正确方法是什么,output以便在迭代不可变字段时可以写入可变字段data?假设通过Generatortrait 的间接性被用于与其他结构共享类似的逻辑,因此MyStruct从trait的默认方法实现访问的字段需要通过这样的访问器方法来完成.
这是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 foo\n"),
_ => self.append("it was something else\n"),
}
}
// 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);
}
Run Code Online (Sandbox Code Playgroud)
要意识到的是编译器是正确的抱怨.想象一下,如果调用output()具有改变返回值引用的东西的副作用data() 那么你在循环中使用的迭代器可能会失效.你的特质函数有隐式契约,他们没有做那样的事情,但没有办法检查这个.因此,您唯一能做的就是暂时取消对数据的完全控制.
当然,这种模式打破了放松的安全性; 循环中的恐慌会使数据移出.
假设通过特征的间接寻址
Generator用于与其他结构共享类似的逻辑,因此MyStruct需要通过像这样的访问器方法来访问特征的默认方法实现中的字段。
那么就不可能了。
当编译器直接看到这些字段时,它会识别对不同字段的访问;它不会打破抽象边界来查看调用的函数内部。
已经有关于在方法上添加属性的讨论,以明确指出哪个字段由哪个方法访问:
但是...这是针对非虚拟方法的。
对于特征来说,这会变得更加复杂,因为特征没有字段,并且每个实现者可能有一组不同的字段!
那么现在怎么办?
您将需要更改您的代码:
append,强制用户使用内部可变性| 归档时间: |
|
| 查看次数: |
751 次 |
| 最近记录: |