【问题标题】:Lifetime Errors using filter_map使用 filter_map 的生命周期错误
【发布时间】:2023-03-24 17:55:02
【问题描述】:

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

【问题讨论】:

    标签: rust lifetime


    【解决方案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,并允许您再次调用闭包,这可能会返回与先前返回的相同的可变引用,从而导致可变引用的别名,这在安全中是被禁止的生锈。

    为此,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)。您的原始闭包将包含 &amp;mut &amp;mut HashMap&lt;i32, i32&gt; 字段,而不是 &amp;mut HashMap&lt;i32, i32&gt; 字段。

    【讨论】:

      【解决方案2】:

      我不知道你为什么要把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; 
              }
          }
      }
      

      你的第一个解决方案有一个大问题——如果你有两次相同的键会发生什么?由于您正在生成可变引用,因此您将拥有一个迭代器,该迭代器将两次分发相同的可变引用,引入 aliasing。这在安全 Rust 中是被禁止的。

      我认为这是您的错误消息的原因,但我承认没有完全将其固定在该问题上。我知道它最终会导致问题。


      我没有看到锯齿效果。我们正在使用get_mut 生成可变引用,但据我所知,这些引用的生命周期不应重叠。除非我遗漏了什么,否则它遵循与您的代码相同的逻辑。

      为了更明显,这是您的相同代码,添加了collect

      let xs: Vec<_> = keys.iter().filter_map(|index| map.get_mut(index)).collect();
      

      重要的内部部分是相同的 - 您正在生成一个迭代器,可能包含对同一项目的多个可变引用。

      请注意,Rust 编译器无法(或至少不会)完全分析您的程序以查看 在这种情况下您消耗了一个值并将其丢弃,从不保留多个值。它所能做的就是看到你所做的部分可能导致这种情况并阻止你这样做。

      相反,上述版本永远不会导致别名,因为可变引用不会持续到 for 块之外。

      【讨论】:

      • 我没有看到锯齿效果。我们正在使用 get_mut 生成可变引用,但据我所知,这些引用的生命周期不应重叠。除非我遗漏了什么,否则它遵循与您的代码相同的逻辑。
      猜你喜欢
      • 2017-05-03
      • 2013-11-26
      • 2017-12-27
      • 2014-09-10
      • 2013-09-24
      • 2016-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多