如何在 rust 中查找或插入 Vec

How to find or insert into a Vec in rust

我正在尝试编写一个函数来查找 returns 对 Vec 中现有元素的可变引用,或者如果它不存在则将其插入,并且 returns 对 Vec 中现有元素的可变引用新元素。

我试过几次,但借阅检查员不相信。我已将我尝试编写的代码简化为下面的示例,它给出了相同的错误。

fn mut_find_or_insert<T: PartialEq>(vec: &mut Vec<T>, val: T) -> &mut T {
    if let Some(u) = vec.iter_mut().find(|u| **u == val) {
        u
    } else {
        vec.push(val);
        vec.last_mut().unwrap()
    }
}

游乐场link:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cb12c38bcf3682b15a247d14aab48b6b

Rust 给我以下编译器错误(完整消息来自 playground link):

error[E0499]: cannot borrow `*vec` as mutable more than once at a time

这似乎应该可以在 Rust 中实现,但是我不清楚如何重新实现它以避免借用检查器错误。

Vec 是一种无序的,不是很结构化的类型。它无法查找项目在其中的确切位置;默认函数最接近的是 contains(),它只告诉您该项目是否包含。

此外,由于 Vec 不是 Set,"find the item or append and return " 的行为未定义 - "find the item",如果存在重复,需要进一步定义。

在不更改为正确类型的情况下解决此问题(HashSet is the type you really want for this. Note the existence of get_or_insert(), which is literally what you are after. It pays to use the proper structure for the job, rather than to try to make everything fit a Vec), we're going to have to build it ourselves. Keeping to your signature, it looks like this (Playground):

trait VecSeekOrAppend<T:PartialEq>:Sized {
    fn get_or_insert(&mut self, item: T) -> &mut T;
}

impl<T> VecSeekOrAppend<T> for Vec<T> 
    where T: PartialEq + Clone {

    fn get_or_insert(&mut self, item: T) -> &mut T {
        if !self.contains(&item) {
            self.push(item.clone());
        }
        for i in self.iter_mut() {
            if i == &mut item {
                return i;
            }
        }
        unreachable!();
    }
}

您的初始版本不起作用的原因是由于返回的生命周期要求;所有从 Vec 返回引用的方法都需要在使用期间有效。通过返回这样一个 &mut 引用,如果您尝试一次完成它,那么 Vec<_> 的突变将在已经有一个可变借用的情况下发生。

将循环一分为二,并执行插入(不保留引用)然后找到引用,可以让我们回避这个问题。执行此操作的另一种方法是通过可序列化或可散列的标识符存储项目(HashMapHashSet 工作的确切方式)以便天生提供这一层间接。

作品中有一个 rust 功能可以缓解一些这种痛苦 (non-lexical lifetimes),但是,正如您从 github 问题中看到的那样,它不会在不久的将来出现。

这不能像写的那样工作的原因是因为当前借用检查器的限制。这与 NLL case #3 非常相似,其中当借用仅在一个分支中使用时,编译器对整个 match 语句过度借用。使用实验性的“Polonius”借用检查器(在带有 -Z polonius 标志的夜间编译器上可用),您的代码将按原样接受。

在稳定的编译器中工作,重新设计数据结构可能是个好主意,正如 也建议的那样,但是如果您需要使用 Vec 来完成这项工作,您可以变通它通过简单地使用索引来结束借用:

fn mut_find_or_insert<T: PartialEq>(vec: &mut Vec<T>, val: T) -> &mut T {
    if let Some(i) = vec.iter().position(|each| *each == val) {
        &mut vec[i]
    } else {
        vec.push(val);
        vec.last_mut().unwrap()
    }
}

这是有效的,因为调用 position 的结果不是引用,所以 vec 的借用在 if let 期间不保留。

这类似于以下问题,它们使用循环中的早期 return 设法找到相同的限制: