具有迭代器绑定的通用特征的生命周期不匹配

Lifetime mismatch in generic trait with iterator bound

我正在尝试将接口抽象为映射类型的支持数据结构(当前为 std::collections::HashMapstd::collections::BTreeMap),以便我可以在不影响调用代码的情况下换出数据结构。我 运行 因一种特定方法而陷入终身错误:(playground)

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

pub trait GroupedCollection<K, V, KeyIterator>
where
    KeyIterator: Iterator
{
    fn keys(&self) -> KeyIterator;
}

impl<K, V> GroupedCollection<K, V, Keys<'_, K, Vec<V>>> for HashMap<K, Vec<V>>
{
    fn keys(&self) -> Keys<'_, K, Vec<V>> {
        HashMap::keys(&self)
    }
}

fn main() {}

编译器告诉我生命周期不匹配:

error: `impl` item signature doesn't match `trait` item signature
  --> src/main.rs:12:5
   |
7  |     fn keys(&self) -> KeyIterator;
   |     ------------------------------ expected `fn(&'1 HashMap<K, Vec<V>>) -> std::collections::hash_map::Keys<'2, K, Vec<V>>`
...
12 |     fn keys(&self) -> Keys<'_, K, Vec<V>> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(&'1 HashMap<K, Vec<V>>) -> std::collections::hash_map::Keys<'1, K, Vec<V>>`
   |
   = note: expected `fn(&'1 HashMap<K, Vec<V>>) -> std::collections::hash_map::Keys<'2, K, Vec<V>>`
              found `fn(&'1 HashMap<K, Vec<V>>) -> std::collections::hash_map::Keys<'1, K, Vec<V>>`
help: the lifetime requirements from the `impl` do not correspond to the requirements in the `trait`
  --> src/main.rs:7:23
   |
7  |     fn keys(&self) -> KeyIterator;
   |                       ^^^^^^^^^^^ consider borrowing this type parameter in the trait

我对预留生命周期的工作不多'_。通过阅读 The Rust Reference: Lifetime Elision,如果我理解正确(但我不确定我是否理解正确),则适用以下内容:

Default Trait Object Lifetimes

...

If the trait object is used as a type argument of a generic type then the containing type is first used to try to infer a bound.

  • If there is a unique bound from the containing type then that is the default

...

我的理解是,这是编译器在 impl 签名上为两个生命周期参数派生 '1 的地方。

由于我没有在特征定义中向编译器提供任何关于生命周期的信息,我的理解是编译器假定 fn keys() 中的 &selftype Item = &'a K单态化 hash_map::Keys 将具有不同的生命周期。

看完, I think I might have arrived at the right solution, but it's melting my brain a bit: (playground)

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

pub trait GroupedCollection<'a, K: 'a, V, KeyIterator>
where
    KeyIterator: Iterator<Item = &'a K>,
{
    fn keys(&'a self) -> KeyIterator;
}

impl<'a, K, V> GroupedCollection<'a, K, V, Keys<'a, K, Vec<V>>> for HashMap<K, Vec<V>>
{
    fn keys(&'a self) -> Keys<K, Vec<V>> {
        HashMap::keys(&self)
    }
}

我对问题的理解是否正确?在此处添加显式生命周期 'a 是正确的解决方案,还是我做了一些碰巧编译的愚蠢事情?

写一生'_的意思是:“这里我应该写一生,但我不是特别在意,所以……随便吧”。

特别是,当你将它写在一个 trait impl 中时,例如:

impl<K, V> GroupedCollection<K, V, Keys<'_, K, Vec<V>>> for ...

就好像你写了:

impl<'x, K, V> GroupedCollection<K, V, Keys<'x, K, Vec<V>>> for ...

也就是说,这个 impl 的用户可以用他们想要的任何生命周期来实例化它。

但是当你把它写成return类型的函数时比如:

fn keys(&self) -> Keys<'_, K, Vec<V>>

就像你写的一样:

fn keys<'s>(&'s self) -> Keys<'s, K, Vec<V>>

即按照默认的生命周期规则推导出生命周期。

现在,你的第一个编译器错误发生了,因为你的 trait 参数的 Keys<'x, _, _> 和你的 Keys<'s, _, _> 不一样,所以函数的类型 keys() 不匹配特征定义。

如何解决?那么,您的 keys 实现必须 return 一个生命周期限制为 self 的值,因此如果您愿意更改特征以反映这一点:

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

impl<'a, K, V> GroupedCollection<'a, K, V, Keys<'a, K, Vec<V>>> for HashMap<K, Vec<V>>
{
    fn keys(&'a self) -> Keys<'a, K, Vec<V>> {
        HashMap::keys(&self)
    }
}

这几乎就是您在第二个版本中所写的内容(我认为您添加了一些不需要的约束)。


根据上面@cdhowie 的评论,您真正想要的是通用关联类型 (GAT)。这意味着决定生命周期的是特征的实现,而不是用户。不幸的是,它们仍然不稳定:

#![feature(generic_associated_types)]

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

pub trait GroupedCollection<K, V>
{
    type KeyIterator<'s> where Self: 's;
    fn keys<'s>(&'s self) -> Self::KeyIterator<'s>;
}

impl<K, V> GroupedCollection<K, V> for HashMap<K, Vec<V>>
{
    type KeyIterator<'s> = Keys<'s, K, Vec<V>>
        where Self: 's;
    fn keys<'s>(&'s self) -> Keys<'s, K, Vec<V>> {
        HashMap::keys(&self)
    }
}

我认为它更容易理解和使用。