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

问题描述 投票:24回答:3

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

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

rust lifetime
3个回答
26
投票

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

Add a lifetime to your trait

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

Implement the trait for a reference to your type

不是为您的类型实现特征,而是实现它以引用您的类型。这种特性从来不需要了解有关生命的任何事情。

然后,特征函数必须按值获取其参数。在您的情况下,您将实现特征作为参考:

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
投票

在未来,你将want为你的一生associated type constructor 'a但Rust还不支持。见RFC 1598


1
投票

Use a wrapper type

如果特征及其所有实现都在一个包中定义,那么帮助器类型可能很有用:

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绑定不能编码到特征的定义中,因此需要将其指定为想要迭代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());
    }
}

Associated type for an internal object providing desired functionality

特征可以定义一种关联类型,该类型允许访问对象的一部分,该对象在引用中绑定,提供必要的访问特征。

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

该解决方案允许在特征定义包之外的实现。通用使用站点的绑定工作与之前的解决方案一样烦人。实现类型可能需要一个内部shell结构,其唯一目的是提供相关类型,以防使用站点边界不像所讨论的示例中的VecIntoIterator那样容易满足。

© www.soinside.com 2019 - 2024. All rights reserved.