我正在尝试在Rust中使用Iterator的filter_map
函数和HashMap
,但我无法编译它。假设我有一个HashMap
和一个键列表。对于每个键,如果地图包含键,我会改变地图中的相应值。例如,假设值为i32
类型,我想增加适当的值。
use std::collections::HashMap;
fn increment(map: &mut HashMap<i32, i32>, keys: &[i32]) {
for value in keys.iter().filter_map(|index| map.get_mut(index)) {
*value += 1;
}
}
fn main() {
let mut map = HashMap::new();
map.insert(1,2);
map.insert(4,5);
increment(&mut map, &[0, 1, 2]);
assert!(*map.get(&1).unwrap() == 3);
assert!(*map.get(&4).unwrap() == 5);
}
此代码给出了与生命周期相关的错误:
<anon>:4:57: 4:71 error: cannot infer an appropriate lifetime for autoref due to conflicting requirements
<anon>:4 for value in keys.iter().filter_map(|index| map.get_mut(index)) {
^~~~~~~~~~~~~~
<anon>:4:9: 6:10 note: in this expansion of for loop expansion
<anon>:4:9: 6:10 note: first, the lifetime cannot outlive the call at 4:8...
<anon>:4 for value in keys.iter().filter_map(|index| map.get_mut(index)) {
<anon>:5 *value += 1;
<anon>:6 }
<anon>:4:9: 6:10 note: in this expansion of for loop expansion
<anon>:4:9: 6:10 note: ...so that argument is valid for the call
<anon>:4 for value in keys.iter().filter_map(|index| map.get_mut(index)) {
<anon>:5 *value += 1;
<anon>:6 }
<anon>:4:9: 6:10 note: in this expansion of for loop expansion
<anon>:4:53: 4:71 note: but, the lifetime must be valid for the method call at 4:52...
<anon>:4 for value in keys.iter().filter_map(|index| map.get_mut(index)) {
^~~~~~~~~~~~~~~~~~
<anon>:4:9: 6:10 note: in this expansion of for loop expansion
<anon>:4:53: 4:56 note: ...so that method receiver is valid for the method call
<anon>:4 for value in keys.iter().filter_map(|index| map.get_mut(index)) {
^~~
<anon>:4:9: 6:10 note: in this expansion of for loop expansion
error: aborting due to previous error
为什么我会收到此错误,使用惯用Rust处理这种情况的最佳方法是什么?
答案 0 :(得分:1)
我不知道你为什么要把filter_map
带到这里。简单地迭代键并设置值对我来说更加明显:
fn increment(map: &mut HashMap<i32, i32>, keys: &[i32]) {
for index in keys {
if let Some(value) = map.get_mut(index) {
*value += 1;
}
}
}
你的第一个解决方案有一个大问题 - 如果你有两次相同的密钥会发生什么?由于您正在生成可变引用,因此您将拥有一个迭代器,该迭代器将分发两次相同的可变引用,引入别名。这是禁止安全的Rust。
我认为这是您的错误消息的原因,但我承认不完全将其固定到该问题。我知道它最终会引起问题。
我没有看到别名效果。我们正在使用
get_mut
生成可变引用,但这些引用的生命周期不应该与我所知的重叠。除非我遗漏了某些东西,否则它遵循与代码相同的逻辑。
为了更明显,这里是相同的代码,collect
加上:
let xs: Vec<_> = keys.iter().filter_map(|index| map.get_mut(index)).collect();
重要的内部部分是相同的 - 您正在生成一个迭代器,可能包含对同一项的多个可变引用。
请注意,Rust编译器不能(或者至少不能)完全分析您的程序,以便在这种情况下使用消耗一个值并将其丢弃,从不保留多个值。它能做的就是看到你所做的部分可以导致这种情况并阻止你这样做。
相反,上面的版本永远不会导致别名,因为可变引用不会超出for
块。
答案 1 :(得分:1)
I've desugared your original code †以获得更准确的错误。我最终在以下方法上出错:
impl<'a, 'b> FnMut<(&'b i32,)> for MyClosure<'a> {
extern "rust-call"
fn call_mut(&mut self, (index,): (&'b i32,)) -> Option<&'a mut i32> {
self.map.get_mut(index)
}
}
错误是:
<anon>:21:18: 21:32 error: cannot infer an appropriate lifetime for autoref due to conflicting requirements [E0495]
<anon>:21 self.map.get_mut(index)
^~~~~~~~~~~~~~
通常,当您从函数返回可变引用时,它将绑定到参数的生命周期。返回的引用导致作为函数的参数传递的值仍被视为借用,并且根据Rust的规则,在第一次借用超出范围之前,您不能再对该值进行另一次借用。因此,该程序无法编译:
struct Foo {
x: i32
}
impl Foo {
fn x_mut(&mut self) -> &mut i32 { &mut self.x }
}
fn main() {
let mut foo = Foo { x: 0 };
let a = foo.x_mut();
foo.x_mut(); // error: cannot borrow `foo` as mutable more than once at a time
}
问题在于您尝试返回带有生命周期'a
的可变引用,但该生命周期并未正确表达您实际上是从MyClosure
借用的事实。因此,编译器不会考虑调用后借用的MyClosure
,并允许再次调用闭包,这可能会返回与之前返回的相同的可变引用,从而导致可变引用的别名,这是禁止的在安全的Rust。
为了实现这一点,FnMut
实现必须以这种方式编写:
impl<'a, 'b> FnMut<(&'b i32,)> for MyClosure<'a> {
extern "rust-call"
fn call_mut<'c>(&'c mut self, (index,): (&'b i32,)) -> Option<&'c mut i32> {
self.map.get_mut(index)
}
}
但这无效:
<anon>:19:5: 22:6 error: method `call_mut` has an incompatible type for trait:
expected bound lifetime parameter ,
found concrete lifetime [E0053]
<anon>:19 extern "rust-call"
<anon>:20 fn call_mut<'c>(&'c mut self, (index,): (&'b i32,)) -> Option<&'c mut i32> {
<anon>:21 self.map.get_mut(index)
<anon>:22 }
这与尝试编写流式迭代器时生成的错误相同。
†实际上,这个脱落的代码对应于闭包move |index| map.get_mut(index)
。您的原始关闭字段将包含&mut &mut HashMap<i32, i32>
字段,而不是&mut HashMap<i32, i32>
字段。