【问题标题】:How do I modify a Vector based on information from an item in the Vector?如何根据 Vector 中项目的信息修改 Vector?
【发布时间】:2020-06-14 13:52:04
【问题描述】:

如何根据来自Vec 中的项目的信息修改Vec,而不具有对向量的不可变和可变引用?

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

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

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

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

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 结构能够根据其中一项中包含的信息(即不可变借用self.items)。

这是代码的playground example

使用Clone

@Stargateur 建议我尝试克隆 build() 中的项目。虽然这确实有效,但出于性能原因,我一直在尝试不克隆项目。 更新:在没有使用Item::New 添加Vec&lt;Item&gt; 修改功能的情况下,我在我的真实代码中实现了clone() 方法,并克隆了与上面示例build() 方法等效的值。当我使用self.items.get(idx).unwrap().clone()self.items.get(idx).unwrap() 时,我看到性能下降了 12 倍。我将继续寻找其他解决方案。问题是,我对 Rust 还比较陌生,即使使用不安全的代码也不知道如何改变规则/做其他事情。

有效的代码 (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();

【问题讨论】:

  • 嗯,它在某些方面很相似,但我想我最终会遇到同样的错误,创建一个中间结构 - 我仍然必须引用 Vec&lt;Item&gt; 中的一个项目才能构造中间结构。有趣的是,在我的真实代码中 Builder is 已经是中间结构
  • 哦,是的,您的情况也有问题,因为您不能将向量借用为不可变的,并且他们对其进行了修改。您需要克隆项目以避免借用,或者您需要处理 build 中的匹配项,因为编译器无法知道修改向量时借用可能会结束 (play.rust-lang.org/…)。
  • 我会尝试克隆值 - 出于性能原因,我一直在努力避免克隆。
  • @Stargateur 我已经更新了我的问题以包含一个潜在的解决方案,该解决方案为Item 实现了Clone 特征。这确实有效,但我不确定我的实际项目会对性能造成多大的影响。如果我没有提出任何其他建议,我会将其标记为答案(如果您将其设为单独的答案)。
  • 您能否提供更多背景信息,以解释您真正想要做什么?因为可能有更好的方法来构建数据以获得更好的性能,而不会遇到借位检查错误。例如,一种可能性是使用竞技场而不是Vec,但如果没有更多上下文,很难知道这是否适合您。这里要认识到的一件重要的事情是,Rust 不会因为编译器错误而令人讨厌:它可以保护您免受实际内存损坏,当 Vec 重新分配并使您的 &Item 悬空时会发生这种情况。

标签: vector rust borrow-checker


【解决方案1】:

每当遇到这样的问题(在使用 Rust 时您会相对频繁地遇到),主要目标应该是将需要不可变借用的代码与需要可变借用的代码隔离开来。如果从build 中的items vec 借用是不可避免的(即您不能将项目移出self.items 或复制/克隆它)并且您必须将对该项目的引用传递给build_item,您可能想考虑将您的build_item 函数重写为 变异self。在这种情况下,build_item 只会将新项目附加到 self.items 的末尾,这让我们可以进行有趣的重构:与其让 build_item 修改 items,不如让它返回要添加到原始项目的项目向量,然后让调用者将新生成的项目添加到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,并创建了一个使用 generate_items 的新 build_item 函数。

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

Playground

【讨论】:

  • 这是有道理的,让我考虑到我能接受的确切用例/限制。仅在调用generate_items() 之后添加新项目意味着新项目在generate_items() 返回之前不可用。不过,我的真实代码的优点/缺点超出了这个问题的范围 - 感谢您的回答/帮助!至少可以说,学习 Rust 是一次冒险:^)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-27
  • 1970-01-01
  • 2014-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多