How do I modify a Vector based on information from an item in the Vector?
如何在Vec中的项目信息基础上修改Vec,而又不具有对向量的不变且可变的引用?
我尝试创建一个最小的示例来演示我的特定问题。在我的实际代码中,Builder结构已经是其他答案提出的中间结构。具体而言,我认为此问题不会由其他问题回答,因为:
-
为什么通过提取方法进行重构会触发借用检查器错误? -我将在中间结构中放入什么?我没有从Vec<Item>计算单独的值。要修改/操作的值是Vec<Item>,它是中间结构中需要的值
假设我有一个项目定义列表,其中项目是字符串,Item的嵌套列表,或者指示应将新项目添加到正在处理的项目列表中:
1 2 3 4 5
| enum Item {
Direct(String),
Nested(Vec<Item>),
New(String),
} |
还有一个构建器,其中包含一个Vec<Item>列表,并在指定的索引处构建一个项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| struct Builder {
items: Vec<Item>,
}
impl Builder {
pub fn build_item(&mut self, item: &Item, output: &mut String) {
match item {
Item::Direct(v) => output.push_str(v),
Item::Nested(v) => {
for sub_item in v.iter() {
self.build_item(sub_item, output);
}
}
Item::New(v) => self.items.push(Item::Direct(v.clone())),
}
}
pub fn build(&mut self, idx: usize, output: &mut String) {
let item = self.items.get(idx).unwrap();
self.build_item(item, output);
}
} |
由于错误而无法编译:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:26:9
|
25 | let item = self.items.get(idx).unwrap();
| ---------- immutable borrow occurs here
26 | self.build_item(item, output);
| ^^^^^----------^^^^^^^^^^^^^^
| | |
| | immutable borrow later used by call
| mutable borrow occurs here
error: aborting due to previous error
For more information about this error, try `rustc --explain E0502`. |
我不知道如何基于其中一项的信息(例如,不可变地借用)修改items结构(即对self.items有可变引用)。 self.items)。
这是代码的操场示例。
使用Clone
@Stargateur建议我尝试克隆build()中的项目。尽管这确实可行,但由于性能原因,我一直在尝试不克隆项目。更新:我没有在Item::New中添加Vec<Item>修改功能,而是在我的真实代码中实现了clone()方法,并克隆了与上述示例build()方法等效的值。当我执行self.items.get(idx).unwrap().clone()与self.items.get(idx).unwrap()时,性能下降了12倍。我将继续寻找其他解决方案。问题是,我对Rust还是比较陌生,甚至不确定如何使用不安全的代码也不确定如何改变规则/做其他事情。
有效的代码(操场)
1 2 3 4 5 6 7 8 9
| impl Clone for Item {
fn clone(&self) -> Self {
match self {
Item::Direct(v) => Item::Direct(v.clone()),
Item::Nested(v) => Item::Nested(v.clone()),
Item::New(v) => Item::New(v.clone()),
}
}
} |
并更改build首先克隆该项目:
1
| let item = self.items.get(idx).unwrap().clone(); |
- 嗯,它在某些方面相似,但我认为在创建中间结构时会遇到相同的错误-Id仍必须引用Vec<Item>中的项才能构造中间结构。有趣的是,在我的真实代码中,Builder已经是中间结构
-
哦,是的,您的案例也有问题,因为您不能借用不可变的向量,因此他们对其进行了修改。您需要克隆项目以避免借用,或者需要处理build中的匹配,因为编译器无法知道在修改向量(play.rust-lang.org/)时借用可能会结束。
-
我会尝试克隆值-出于性能考虑,克隆是我一直在努力避免的事情。
-
@Stargateur Ive更新了我的问题,包括一个可能的解决方案,该解决方案实现了Item的Clone特征。这确实有效,但是我不确定我的真实项目会带来多大的性能提升。如果我没有提出其他建议,请把它标记为答案(如果您将其作为单独的答案)。
-
@Stargateur"处理构建中的比赛"是什么意思?您是说要合并build和build_item函数吗?这在某些情况下会起作用-Im正在工作的真实代码是递归的,其中某些Item包含Item的列表,每个列表都是由build_item函数以递归方式构建的。
-
每个递归都可以写成命令,但是,如果没有更具体的例子,我不能为您提供更多帮助,您可能会用错误的解决方案来实现您的工具。您的问题很好,但是1.这里不清楚您的目的是什么2.您忘记显示要使用递归。
-
好点@Stargateur!病态更新问题。我试图在不引入递归方面的情况下尽量减少问题。
-
@Stargateur再次感谢您的帮助!我用clone()和没有clone()(删除了<Item::New功能)对真实代码进行了基准测试,发现perf降低了12倍:^(我一直在寻找解决方案
-
您能否提供更多背景信息,以说明您真正想做的是什么?因为可能会有更好的方法来结构化数据以获得更好的性能,而不会遇到借阅检查错误。例如,一种可能性是使用竞技场而不是Vec,但是如果没有更多上下文,很难知道这是否适合您。这里要认识到的一件重要事情是,Rust在这里不会因为编译器错误而造成麻烦:它可以保护您免受当Vec重新分配并离开您的内存时发生的实际内存损坏的影响。
-
每当遇到这样的问题(使用Rust时,您会相对经常遇到)时,主要目标应该是将需要不可变借位的代码与需要可变借位的代码隔离开。如果不可避免地要从build中的items vec借用(即,您不能将该项移出self.items,也不能将其复制/克隆),并且您必须将该项目的引用传递给build_item,则可能想考虑重写您的build_item函数以不使self突变。在这种情况下,build_item只会在self.items的末尾附加新项目,这使我们可以进行有趣的重构:不必让build_item修改items,而是使其返回要添加到原始项目的项目。向量,然后让调用者将新生成的项目添加到items向量中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| impl Builder {
fn generate_items(&self, item: &Item, output: &mut String) -> Vec<Item> {
match item {
Item::Direct(v) => {
output.push_str(v);
Vec::new()
}
Item::Nested(v) => {
v.iter()
.flat_map(|sub_item| self.generate_items(sub_item, output))
.collect()
}
Item::New(v) => vec![Item::Direct(v.clone())],
}
}
pub fn build_item(&mut self, item: &Item, output: &mut String) {
let mut new_items = self.generate_items(item, output);
self.items.append(&mut new_items);
}
pub fn build(&mut self, idx: usize, output: &mut String) {
// Non lexical lifetimes allow this to compile, as the compiler
// realizes that `item` borrow can be dropped before the mutable borrow
// Immutable borrow of self starts here
let item = self.items.get(idx).unwrap();
let mut new_items = self.generate_items(item, output);
// Immutable borrow of self ends here
// Mutable borrow of self starts here
self.items.append(&mut new_items);
}
} |
请注意,为了保留API,您的build_item函数已重命名为generate_items,并且创建了一个新的使用generate_items的build_item函数。
如果仔细看,您会发现generate_items甚至不需要self,并且可以是Builder中的独立函数或静态函数。
游乐场
-
每当遇到这样的问题(使用Rust时,您会相对经常遇到)时,主要目标应该是将需要不可变借位的代码与需要可变借位的代码隔离开。如果不可避免地要从build中的items vec借用(即,您不能将该项移出self.items,也不能将其复制/克隆),并且您必须将该项目的引用传递给build_item,则可能想考虑重写您的build_item函数以不使self突变。在这种情况下,build_item只会在self.items的末尾附加新项目,这使我们可以进行有趣的重构:不必让build_item修改items,而是使其返回要添加到原始项目的项目。向量,然后让调用者将新生成的项目添加到items向量中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| impl Builder {
fn generate_items(&self, item: &Item, output: &mut String) -> Vec<Item> {
match item {
Item::Direct(v) => {
output.push_str(v);
Vec::new()
}
Item::Nested(v) => {
v.iter()
.flat_map(|sub_item| self.generate_items(sub_item, output))
.collect()
}
Item::New(v) => vec![Item::Direct(v.clone())],
}
}
pub fn build_item(&mut self, item: &Item, output: &mut String) {
let mut new_items = self.generate_items(item, output);
self.items.append(&mut new_items);
}
pub fn build(&mut self, idx: usize, output: &mut String) {
// Non lexical lifetimes allow this to compile, as the compiler
// realizes that `item` borrow can be dropped before the mutable borrow
// Immutable borrow of self starts here
let item = self.items.get(idx).unwrap();
let mut new_items = self.generate_items(item, output);
// Immutable borrow of self ends here
// Mutable borrow of self starts here
self.items.append(&mut new_items);
}
} |
请注意,为了保留API,您的build_item函数已重命名为generate_items,并且创建了一个新的使用generate_items的build_item函数。
如果仔细看,您会发现generate_items甚至不需要self,并且可以是Builder中的独立函数或静态函数。
游乐场
-
每当遇到这样的问题(使用Rust时,您会相对经常遇到)时,主要目标应该是将需要不可变借位的代码与需要可变借位的代码隔离开。如果不可避免地要从build中的items vec借用(即,您不能将该项移出self.items,也不能将其复制/克隆),并且您必须将该项目的引用传递给build_item,则可能想考虑重写您的build_item函数以不使self突变。在这种情况下,build_item只会在self.items的末尾附加新项目,这使我们可以进行有趣的重构:不必让build_item修改items,而是使其返回要添加到原始项目的项目。向量,然后让调用者将新生成的项目添加到items向量中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| impl Builder {
fn generate_items(&self, item: &Item, output: &mut String) -> Vec<Item> {
match item {
Item::Direct(v) => {
output.push_str(v);
Vec::new()
}
Item::Nested(v) => {
v.iter()
.flat_map(|sub_item| self.generate_items(sub_item, output))
.collect()
}
Item::New(v) => vec![Item::Direct(v.clone())],
}
}
pub fn build_item(&mut self, item: &Item, output: &mut String) {
let mut new_items = self.generate_items(item, output);
self.items.append(&mut new_items);
}
pub fn build(&mut self, idx: usize, output: &mut String) {
// Non lexical lifetimes allow this to compile, as the compiler
// realizes that `item` borrow can be dropped before the mutable borrow
// Immutable borrow of self starts here
let item = self.items.get(idx).unwrap();
let mut new_items = self.generate_items(item, output);
// Immutable borrow of self ends here
// Mutable borrow of self starts here
self.items.append(&mut new_items);
}
} |
请注意,为了保留API,您的build_item函数已重命名为generate_items,并且创建了一个新的使用generate_items的build_item函数。
如果仔细看,您会发现generate_items甚至不需要self,并且可以是Builder中的独立函数或静态函数。
游乐场
每当遇到这样的问题(使用Rust时,您会相对经常遇到)时,主要目标应该是将需要不可变借位的代码与需要可变借位的代码隔离开。如果不可避免地要从build中的items vec借用(即,您不能将该项移出self.items,也不能将其复制/克隆),并且您必须将该项目的引用传递给build_item,则可能想考虑重写您的build_item函数以不使self突变。在这种情况下,build_item只会在self.items的末尾附加新项目,这使我们可以进行有趣的重构:不必让build_item修改items,而是使其返回要添加到原始项目的项目。向量,然后让调用者将新生成的项目添加到items向量中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| impl Builder {
fn generate_items(&self, item: &Item, output: &mut String) -> Vec<Item> {
match item {
Item::Direct(v) => {
output.push_str(v);
Vec::new()
}
Item::Nested(v) => {
v.iter()
.flat_map(|sub_item| self.generate_items(sub_item, output))
.collect()
}
Item::New(v) => vec![Item::Direct(v.clone())],
}
}
pub fn build_item(&mut self, item: &Item, output: &mut String) {
let mut new_items = self.generate_items(item, output);
self.items.append(&mut new_items);
}
pub fn build(&mut self, idx: usize, output: &mut String) {
// Non lexical lifetimes allow this to compile, as the compiler
// realizes that `item` borrow can be dropped before the mutable borrow
// Immutable borrow of self starts here
let item = self.items.get(idx).unwrap();
let mut new_items = self.generate_items(item, output);
// Immutable borrow of self ends here
// Mutable borrow of self starts here
self.items.append(&mut new_items);
}
} |
请注意,为了保留API,您的build_item函数已重命名为generate_items,并且创建了一个新的使用generate_items的build_item函数。
如果仔细看,您会发现generate_items甚至不需要self,并且可以是Builder中的独立函数或静态函数。
游乐场
- 这是有道理的,并让我考虑了我喜欢的确切用例/限制。仅在调用generate_items()之后才添加新项目,这意味着直到generate_items()返回之前,新项目才可用。但是,对于我的实际代码,它的优缺点超出了此问题的范围-谢谢您的回答/帮助!至少可以说,学习Rust是一种冒险:^)