Rust 在迭代另一个不可变字段时对一个字段进行变异
鉴于以下计划:Rust 在迭代另一个不可变字段时对一个字段进行变异,rust,Rust,鉴于以下计划: 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)
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);
}
构造代码的正确方法是什么,以便在不可变字段数据
上迭代时,可以写入可变字段输出
?假设通过生成器
trait的间接寻址用于与其他结构共享类似的逻辑,因此从trait的默认方法实现中访问MyStruct
的字段需要通过如下访问器方法完成
假设通过生成器
trait的间接寻址用于与其他结构共享类似的逻辑,因此从trait的默认方法实现中访问MyStruct
的字段需要通过如下访问器方法完成
那就不可能了
当编译器直接看到不同的字段时,它会识别对这些字段的访问;它不会打破抽象边界来窥视调用的函数内部 已经讨论过在方法上添加属性,以明确指出哪个字段由哪个方法访问:
- 编译器将强制方法不接触属性中未提及的任何字段
- 然后,编译器可以使用所述方法仅对字段子集进行操作的知识
那现在怎么办 您需要更改代码:
- 您可以将特征一分为二,并需要两个对象(一个用于迭代,一个用于变异)
- 您可以“隐藏”append方法的可变性,强制用户使用内部可变性
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);
}
struct数据{
发布项目:Vec{
数据:&'mut数据,
输出:字符串,
}
恳求{
fn生成(mut self)->字符串{
self.generate_items();
自我输出
}
}
恳求{
fn数据(&mut self)->&mut数据{
自我数据
}
fn输出(&mut self)->&mut字符串{
&多自输出
}
}
fn main(){
让mut data=data{
物品:vec![“foo”,“bar”,“baz”],
};
让生成器=MyGenerator{
数据:&mut数据,
输出:String::new(),
};
让output=generator.generate();
println!(“{}”,输出);
}
需要认识到的是,编译器抱怨是对的。想象一下,如果调用output()
会产生副作用,使data()的返回值引用的内容发生变化
然后,循环中使用的迭代器可能会失效。您的trait函数有一个隐式约定,即它们不会执行类似的操作,但无法检查这一点。因此,您唯一能做的就是暂时假定完全控制数据,将其取出
当然,这种模式破坏了放松安全;循环中的恐慌将使数据移出。您可以使用:
RefCell使用Rust的生命周期实现“动态借用”,这是一种
一个人可以声称临时的、独占的、可变的访问权限的过程
“在运行时”跟踪RefCells的内部值,
与Rust完全跟踪的本机引用类型不同
静态地,在编译时。因为RefCell借用是动态的,所以
可以尝试借用已经可变的值
借用;发生这种情况时会导致线程恐慌
使用std::cell::{RefCell,RefMut};
结构数据{
发布项目:Vec{
数据:&'a数据,
输出:RefCell,
}
恳求{
fn生成(自)->字符串{
self.generate_items();
self.output.into_inner()
}
}
恳求{
fn数据(&self)->&data{
自我数据
}
fn输出(&self)->RefMut{
self.output.borrow_mut()
}
}
fn main(){
让数据=数据{
物品:vec![“foo”,“bar”,“baz”],
};
让生成器=MyGenerator{
数据:&数据,
输出:RefCell::new(String::new()),
};
让output=generator.generate();
println!(“{}”,输出);
}
您个人是否喜欢如何处理?使用
RefCell
?将不可变数据和输出字符串拆分为两个单独的对象?去掉特征,只需在一种类型中处理所有内容?@JimmyCuadra:对于这个特定的示例,我会说两个特征和两个结构……但我假设这是一个简化的示例这就是为什么我提出了多种解决方案,并让您来选择最适合您的解决方案。一方面,这需要将数据的定义更改为fn数据(&mut self)->&mut数据
(请注意,到目前为止,它一直保持不变).另一方面,我觉得它相当优雅!对于以后遇到此问题的任何人,我相信此RFC解决了此问题中的问题:
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 foo\n"),
_ => self.append("it was something else\n"),
}
}
}
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);
}