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

mbr*_*brt 27 lifetime rust

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

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>,
}
Run Code Online (Sandbox Code Playgroud)

我想实现以下Foo特征Bar:

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

    fn get(&self) -> Self::Iter {
        self.v.iter()
    }
}
Run Code Online (Sandbox Code Playgroud)

但是我收到了这个错误:

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

我发现无法在相关类型中指定生命周期.特别是我想表达迭代器不能超过self生命周期.

我如何修改Foo特征或Bar特征实现来使其工作?

铁锈操场

oli*_*obk 29

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

为您的特质添加生命周期

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

    fn get(&'a self) -> Self::Iter;
}
Run Code Online (Sandbox Code Playgroud)

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

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()
    }
}
Run Code Online (Sandbox Code Playgroud)

当您需要特征参数的特征时,还需要确保对特征对象的任何引用具有相同的生命周期:

fn fooget<'a, T: Foo<'a>>(foo: &'a T) {}
Run Code Online (Sandbox Code Playgroud)

实现特征以引用您的类型

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

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

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()
    }
}
Run Code Online (Sandbox Code Playgroud)

你的fooget功能现在变得简单了

fn fooget<T: Foo>(foo: T) {}
Run Code Online (Sandbox Code Playgroud)

这个问题是该fooget功能不知道T实际上是一个&Bar.当您调用该get函数时,您实际上正在移出该foo变量.您不会移出对象,只需移动参考.如果您的fooget函数尝试调用get两次,则该函数将无法编译.

如果希望fooget函数只接受Foo为引用实现特征的参数,则需要显式声明此绑定:

fn fooget_twice<'a, T>(foo: &'a T)
where
    &'a T: Foo,
{}
Run Code Online (Sandbox Code Playgroud)

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

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

n fooget_twice<T>(foo: &T)
where
    &T: Foo,
{}
Run Code Online (Sandbox Code Playgroud)

但它不够聪明.


对于更复杂的情况,您可以使用尚未实现的Rust功能:通用关联类型(GAT).问题44265正在跟踪这方面的工作.


mza*_*uev 5

使用包装类型

如果特征及其所有实现都定义在一个包中,则辅助类型可能会很有用:

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

struct IterableFoo<'a, T: ?Sized + Foo>(pub &'a T);
Run Code Online (Sandbox Code Playgroud)

对于实现 的具体类型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()
    }
}
Run Code Online (Sandbox Code Playgroud)

该解决方案不允许在不同的板条箱中实现。另一个缺点是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());
    }
}

Run Code Online (Sandbox Code Playgroud)

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

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

trait Foo {
    type Iterable: ?Sized;

    fn get(&self) -> &Self::Iterable;
}
Run Code Online (Sandbox Code Playgroud)

这要求任何实现类型都包含可以如此公开的部分:

impl Foo for Bar {
    type Iterable = [PathBuf];

    fn get(&self) -> &Self::Iterable {
        &self.v
    }
}
Run Code Online (Sandbox Code Playgroud)

在使用以下结果的泛型代码中对关联类型的引用进行限制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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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