关于rust:如何基于Vector中某个项目的信息来修改Vector?

 2021-04-09 

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();


每当遇到这样的问题(使用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_itemsbuild_item函数。

如果仔细看,您会发现generate_items甚至不需要self,并且可以是Builder中的独立函数或静态函数。

游乐场