通用特征绑定的生命周期问题

Lifetime issue with generic trait bound

我正在写一个特征作为映射类型数据结构的接口(例如 std::collections::BTreeMapstd::collections::HashMap)。这是 的后续,尽管它是独立的。

我遇到了一个似乎无法理解的终生问题。我在我所有的教科书、Rust 参考、Whosebug 等中搜索了答案,但我一直无法弄清楚发生了什么。根据上一个问题的建议,我已经在以下代码上编写了将近十几个变体,但我最终遇到了同样的情况。 我希望有人能帮助我理解为什么 gc3() 是不可能的或者我做错了什么。 我知道这完全有可能是我对这个问题不以为然很长一段时间以来,我都遗漏了一些应该显而易见的简单内容。 (playground)

use std::collections::hash_map::{HashMap, Iter};

fn main() {
    gc1(&HashMap::new());
    gc2(&HashMap::new());
    gc3(HashMap::new());
}

// Works
fn gc1<'a>(map: &'a dyn GroupedCollection<'a, usize, usize, Iter<'a, usize, Vec<usize>>>) {
    let _ = map.iter().collect::<Vec<_>>();
}

// Works
fn gc2<'a, M>(map: &'a M)
where
    M: 'a + GroupedCollection<'a, usize, usize, Iter<'a, usize, Vec<usize>>>,
{
    let _ = map.iter().collect::<Vec<_>>();
}

// Compiler error: `map` does not live long enough
fn gc3<'a, M>(map: M)
where
    M: 'a + GroupedCollection<'a, usize, usize, Iter<'a, usize, Vec<usize>>>,
{
    let _ = map.iter().collect::<Vec<_>>();
}

pub trait GroupedCollection<'a, K, V, I: 'a> {
    fn iter(&'a self) -> I;
}

impl<'a, K, V> GroupedCollection<'a, K, V, Iter<'a, K, Vec<V>>> for HashMap<K, Vec<V>>
{
    fn iter(&'a self) -> Iter<'a, K, Vec<V>> {
        HashMap::iter(&self)
    }
}
error[E0597]: `map` does not live long enough
  --> src/main.rs:27:13
   |
23 | fn gc3<'a, M>(map: M)
   |        -- lifetime `'a` defined here
...
27 |     let _ = map.iter().collect::<Vec<_>>();
   |             ^^^^^^^^^^
   |             |
   |             borrowed value does not live long enough
   |             argument requires that `map` is borrowed for `'a`
28 | }
   | - `map` dropped here while still borrowed

编译器是否因为 map.iter() 生成的引用在 collect() 的末尾被删除而抱怨,因为 collect(self) 消耗了迭代器? (我尝试通过将 'a: 'b 分配给 GroupedCollection 并将 'b 分配给迭代器引用来解决这个问题,但它似乎并没有解决问题:playground

TL;DR: 不要使用生命周期参数,使用 HRTB: where M: for<'a> GroupedCollection<'a, usize, usize, Iter<'a, usize, Vec<usize>>>.


'a 是 caller-chosen 生命周期。假设调用者选择 'static(通常,最好根据 'static 验证生命周期)。 map.iter() 被脱糖为 <M as GroupedCollection<'static, ...>>::iter(&map)<M as GroupedCollection<'static, ...>>::iter() 需要 &'a self,即 &'static self。但是 map 是一个局部变量,因此 &map 肯定是 而不是 'static。轰.

它与引用一起工作,因为调用者不仅选择'a,还需要提供与之匹配的引用。如果它会选择 'static,它将必须提供 &'static M,所以一切都很好。

解决办法?你想要一个 callee-选择的生命周期。也就是说,M 实施 GroupedCollection 一段时间 'a 我选择,而不是我的调用者。在 Rust 中无法表达这一点,但你可以说“M implements GroupedCollection for any lifetime”,显然这包括我将选择的生命周期.这是 Higher-Ranked Trait BoundsM: for<'a> GroupedCollection<'a, ...>。所以:

fn gc3<M>(map: M)
where
    M: for<'a> GroupedCollection<'a, usize, usize, Iter<'a, usize, Vec<usize>>>,
{
    // ...
}

Playground.

完美吗?不。可能存在 Many 生命周期内不实现 GroupedCollection 的情况,但它会在我们选择的生命周期内实现。但是如果没有 GAT,你就无法做得更好。