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

时间:2015-11-16 11:47:30

标签: rust lifetime

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

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>,
}

我想为Foo

实施Bar特征
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特征或Bar特质实施,以使其有效?

Rust playground

3 个答案:

答案 0 :(得分:23)

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

为您的特质添加生命周期

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

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

这要求您在使用特征的任何地方注释生命周期。实现特征时,需要执行通用实现:

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特征的参数,则需要明确说明此边界:

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

where子句确保您只为引用Foo而不是类型的引用调用此函数。也可以为两者实施。

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

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

但它不够聪明yet

对于更复杂的情况,您可以使用尚未实现的Rust功能:Generic Associated Types(GAT)。正在issue 44265中跟踪该工作。

答案 1 :(得分:1)

将来,您want associated type constructor生命'a,但Rust还不支持。见RFC 1598

答案 2 :(得分:0)

使用包装器类型

如果特征及其所有实现都在一个板条箱中定义,则辅助类型可能是有用的:

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()
    }
}

此解决方案不允许以不同的方式进行实施。另一个缺点是无法将IntoIterator绑定编码到特征的定义中,因此需要将其指定为要迭代{的结果的通用代码的附加(更高级别)绑定{1}}:

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 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()); } } Vec那样容易满足时,实现类型可能需要内部shell结构,其唯一目的是提供关联的类型。