我正在尝试编写一个find函数,该函数返回对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()
}
}
Rust给我以下编译器错误(通过游乐场链接的完整消息):
error[E0499]: cannot borrow `*vec` as mutable more than once at a time
这似乎应该可以在rust中实现,但是我不清楚如何重新实现以避免借阅检查器错误。
Vec
是无序的,不是非常结构化的类型。它无法查询项目在其中的确切位置;默认函数最接近的是contains()
,它仅告诉您是否包含该项目。
此外,由于Vec
不是Set
,因此未定义“查找项目或附加并返回”的行为-如果存在重复,则“查找项目”的行为必须为进一步定义。
要解决此问题而不更改为正确的类型(HashSet
是您真正想要的类型。请注意,确实存在HashSet
,这正是您所追求的。为此,请使用正确的结构工作,而不是试图使所有内容都适合get_or_insert()
),我们将不得不自己构建它。保持签名不变,它看起来像这样(get_or_insert()
):
Vec
您的初始版本无法正常运行的原因是由于返回的生命周期要求;从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<_>
的确切工作方式),以便固有地提供此间接层。
[作品中有一个防锈功能可以缓解这种痛苦(HashMap
),但是,正如您从github问题中所看到的那样,这不会在不久的将来出现。
之所以无法按书面要求进行操作,是因为当前借阅检查器存在限制。这非常类似于HashSet
,在这种情况下,当仅在分支之一中使用借用时,编译器会为整个non-lexical lifetimes语句过度借用。使用实验性的“ Polonius”借阅检查器(可在夜间编译器中使用NLL case #3标志使用),您的代码按原样接受。
在稳定的编译器中工作,按照match
的建议重新设计数据结构可能是一个好主意,但是如果您需要使用-Z polonius
进行此工作,则可以通过遍历索引而不是索引来解决。项目:
Sébastien Renauld's answer
这是有效的,因为调用Vec
的结果不是引用,因此在fn mut_find_or_insert<T: PartialEq>(vec: &mut Vec<T>, val: T) -> &mut T {
if let Some(i) = (0..vec.len()).find(|&i| vec[i] == val) {
&mut vec[i]
} else {
vec.push(val);
vec.last_mut().unwrap()
}
}
期间不保留find
的借用。
这类似于以下问题,这些问题使用循环的早期返回设法找到相同的限制:
vec
if let