如何根据 Vector 中某个项目的信息修改 Vector?

How do I modify a Vector based on information from an item in the Vector?

如何根据 Vec 中某项的信息修改 Vec,而无需对矢量进行不可变和可变引用?

我试图创建一个最小示例来演示我的特定问题。在我的真实代码中, Builder 结构已经是其他答案提出的中间结构。具体来说,我认为这个问题没有被其他问题回答,因为:

假设我有一个项目定义列表,其中项目是字符串、Item 的嵌套列表,或者指示应将新项目添加到正在处理的项目列表中:

enum Item {
    Direct(String),
    Nested(Vec<Item>),
    New(String),
}

还有一个构建器,它持有一个 Vec<Item> 列表,并在指定索引处构建一个项目:

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

由于以下错误无法编译:

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`.

我不知道如何让 Builder 结构能够根据其中一个中包含的信息修改其 items(即具有对 self.items 的可变引用)项目(即 self.items 的不可变借用)。

这里是一个playground example的代码。

使用Clone

@Stargateur 建议我尝试克隆 build() 中的项目。虽然这确实有效,但出于性能原因,我一直在尝试不克隆项目。 UPDATE:没有添加 Item::NewVec<Item> 修改功能,我在我的真实代码中实现了 clone() 方法并克隆了相当于上面的示例 build() 方法。当我执行 self.items.get(idx).unwrap().clone()self.items.get(idx).unwrap() 时,我发现性能下降了 12 倍。我将继续寻找其他解决方案。问题是,我对 Rust 还是比较陌生,我不确定如何改变 rules/do 其他东西,即使是不安全的代码。

有效的代码 (playground)

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 以首先克隆项目:

        let item = self.items.get(idx).unwrap().clone();

每当遇到这样的问题(你在使用 Rust 时会相对频繁地遇到),主要目标应该是将需要不可变借用的代码与需要可变借用的代码隔离开来。如果从 build 中的 items vec 借用是不可避免的(即你不能将项目移出 self.items 或 copy/clone 它)并且你必须传递对此的引用build_item,您可能需要考虑将 build_item 函数重写为 not mutate self。在这种情况下,build_item 只会将新项目附加到 self.items 的末尾,这让我们可以进行有趣的重构:与其让 build_item 修改 items,不如将其设为 return 要添加到原始向量的项目,然后让 caller 将新生成的项目添加到 items 向量。

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,并且新的 build_item 函数使用 generate_items 已创建。

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

Playground