如何在关联类型中指定生命周期参数?

58

我有这个特性和简单的结构:

use std::path::{Path, PathBuf};

trait Foo {
    type Item: AsRef<Path>;
    type Iter: Iterator<Item = Self::Item>;

    fn get(&self) -> Self::Iter;
}

struct Bar {
    v: Vec<PathBuf>,
}

我想为Bar实现Footrait:

impl Foo for Bar {
    type Item = PathBuf;
    type Iter = std::slice::Iter<PathBuf>;

    fn get(&self) -> Self::Iter {
        self.v.iter()
    }
}

但是我得到了这个错误:

error[E0106]: missing lifetime specifier
  --> src/main.rs:16:17
   |
16 |     type Iter = std::slice::Iter<PathBuf>;
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^ expected lifetime parameter

我找不到在关联类型内指定生命周期的方法。尤其是我想表达迭代器不能超出self生命周期的范围。

我应该如何修改Foo trait或Bar trait的实现以使其工作?

Rust playground

3个回答

64

有两种解决方案可以解决您的问题。我们从最简单的方案开始:

将生命周期添加到您的特性中

trait Foo<'a> {
    type Item: AsRef<Path>;
    type Iter: Iterator<Item = Self::Item>;
    
    fn get(&'a self) -> Self::Iter;
}

这要求您在使用trait的任何地方注明其生命周期。当您实现trait时,需要进行通用实现:

impl<'a> Foo<'a> for Bar {
    type Item = &'a PathBuf;
    type Iter = std::slice::Iter<'a, PathBuf>;
    
    fn get(&'a self) -> Self::Iter {
        self.v.iter()
    }
}

当你需要一个泛型参数的特性时,你也需要确保对你的特性对象的任何引用具有相同的生命周期:

fn fooget<'a, T: Foo<'a>>(foo: &'a T) {}

为类型的引用实现特性

与其为您的类型实现特性,不如为您的类型的引用实现它。这种方式下,特性不需要知道任何关于生命周期的信息。

因此,特性函数必须通过值来获取其参数。在您的情况下,您将为引用实现特性:

trait Foo {
    type Item: AsRef<Path>;
    type Iter: Iterator<Item = Self::Item>;
    
    fn get(self) -> Self::Iter;
}

impl<'a> Foo for &'a Bar {
    type Item = &'a PathBuf;
    type Iter = std::slice::Iter<'a, PathBuf>;
    
    fn get(self) -> Self::Iter {
        self.v.iter()
    }
}

您的fooget函数现在变得非常简单,只需如下所示:
fn fooget<T: Foo>(foo: T) {}

这样做的问题是,fooget 函数并不知道 T 实际上是 &Bar。当你调用 get 函数时,你实际上正在移出 foo 变量。你没有移出对象,只是移动了引用。如果你的 fooget 函数尝试两次调用 get,那么函数将无法编译。
如果你想让你的 fooget 函数仅接受实现了 Foo trait 的引用作为参数,则需要显式声明此约束。
fn fooget_twice<'a, T>(foo: &'a T)
where
    &'a T: Foo,
{}

where子句确保您仅为引用调用此函数,其中Foo是引用而不是类型的实现。它也可能同时针对两者实现。

从技术上讲,编译器可以自动推断fooget_twice中的生命周期,因此您可以将其编写为

fn fooget_twice<T>(foo: &T)
where
    &T: Foo,
{}

但它还不够智能 (尚未实现)


对于更复杂的情况,您可以使用 Rust 的一个尚未实现的功能: 通用关联类型(GATs)。该工作正在 问题 44265 中跟踪。


我选择使用第一种解决方案,因为从 fooget 类似函数的角度来看,它似乎会带来更少的负担。相对于第二种解决方案,这个特征也更加明确。 - mbrt
有超过两种解决方案。请查看我的答案,其中另外一种解决方案没有这两种解决方案的缺点,尽管在某种程度上有些笨拙。 - mzabaluev
1
你好,你知道如何在第二种方法中引用相关类型T :: Item吗?在这种情况下,T没有实现Foo,只有&'a T:Foo。编辑:我找到了!它是<&T as Foo> :: Item - 张实唯

5

使用包装类型

如果特质和所有实现都在同一个crate中定义,那么使用辅助类型会很有用:

trait Foo {
    fn get<'a>(&'a self) -> IterableFoo<'a, Self> {
        IterableFoo(self)
    }
}

struct IterableFoo<'a, T: ?Sized + Foo>(pub &'a T);

对于实现了 Foo 接口的具体类型,可以在包装它的 IterableFoo 上实现迭代器转换:

impl Foo for Bar {}

impl<'a> IntoIterator for IterableFoo<'a, Bar> {
    type Item = &'a PathBuf;
    type IntoIter = std::slice::Iter<'a, PathBuf>;
    fn into_iter(self) -> Self::IntoIter {
        self.0.v.iter()
    }
}

这种解决方案不允许在不同的crate中实现。另一个缺点是IntoIterator限定无法编码进trait的定义中,因此需要将其作为附加(和更高级别的)限定来指定泛型代码,以便迭代Foo::get的结果:

fn use_foo_get<T>(foo: &T)
where
    T: Foo,
    for<'a> IterableFoo<'a, T>: IntoIterator,
    for<'a> <IterableFoo<'a, T> as IntoIterator>::Item: AsRef<Path>
{
    for p in foo.get() {
        println!("{}", p.as_ref().to_string_lossy());
    }
}

为提供所需功能的内部对象定义关联类型

该trait可以定义一个关联类型,通过绑定在引用中的对象的一部分,提供所需的访问trait。

trait Foo {
    type Iterable: ?Sized;

    fn get(&self) -> &Self::Iterable;
}

这需要任何实现类型都包含一个可以这样公开的部分:
impl Foo for Bar {
    type Iterable = [PathBuf];

    fn get(&self) -> &Self::Iterable {
        &self.v
    }
}

在使用get的泛型代码中,对关联类型的引用设置边界:

fn use_foo_get<'a, T>(foo: &'a T)
where
    T: Foo,
    &'a T::Iterable: IntoIterator,
    <&'a T::Iterable as IntoIterator>::Item: AsRef<Path>
{
    for p in foo.get() {
        println!("{}", p.as_ref().to_string_lossy());
    }
}

这个解决方案允许在特质定义的 crate 之外实现。 与先前的解决方案一样,泛型使用点上的边界工作令人烦恼。 如果使用点的边界不像在例子中讨论的 VecIntoIterator 那样容易满足,那么实现类型可能需要一个内部壳结构体,其唯一目的是提供关联类型。


这是一个有趣的解决方案,但在我看来,它似乎只能在 IterableFoo 和 Bar 定义在同一个 crate 中才能工作,对吗?因此,您不能在由您的 crate 定义的通用 trait 中使用它,而这些 trait 可以由您 crate 的用户在其自己的代码中实现...或者我错过了什么? - Pierre-Antoine
@Pierre-Antoine,我添加了另一种解决方案,允许离线实现。 - mzabaluev

2

将来,您可能会想要一个关联到生命周期'a的类型构造器,但Rust目前还不支持。请参阅RFC 1598以获取更多信息。


1
请注意,相应的跟踪问题最近已关闭。 - bluenote10

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接