使用filter_map的生命周期错误

时间:2015-11-16 02:52:57

标签: rust lifetime

我正在尝试在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处理这种情况的最佳方法是什么?

2 个答案:

答案 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>字段。