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

Posted

技术标签:

【中文标题】如何在关联类型中指定生命周期参数?【英文标题】:How do I specify lifetime parameters in an associated type? 【发布时间】:2016-02-17 12:11:12 【问题描述】:

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

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 实现Foo 特征:

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

【问题讨论】:

【参考方案1】:

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

为你的特质增添一生

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

这要求您在使用该特征的任何地方都对生命周期进行注释。实现 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()
    

当您需要 trait 作为泛型参数时,您还需要确保对您的 trait 对象的任何引用都具有相同的生命周期:

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

实现 trait 以引用您的类型

不要为您的类型实现特征,而是实现它以引用您的类型。 trait 永远不需要以这种方式知道关于生命周期的任何事情。

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

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 实际上是&amp;Bar。当您调用get 函数时,您实际上是在移出foo 变量。您不会移出对象,而只是移动参考。如果您的fooget 函数尝试调用get 两次,该函数将无法编译。

如果您希望 fooget 函数仅接受为引用实现了 Foo 特征的参数,则需要明确声明此界限:

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,

但它不够聪明yet。


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

【讨论】:

我选择使用第一个解决方案,因为从fooget 类似功能的角度来看,它似乎施加的负担更小。在第二种解决方案中,该特征也更加明确。 解决方案不止两种。请参阅我对另一个没有这两者缺点的答案的回答,尽管它以自己的方式很笨拙。 你好,你知道T没有实现Foo,只有&amp;'a T: Foo的第二种方法中如何引用关联类型T::Item吗?编辑:我找到了!这是&lt;&amp;T as Foo&gt;::Item【参考方案2】:

使用包装类型

如果 trait 及其所有实现都定义在一个 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 绑定不能被编码到特征的定义中,因此需要将它指定为想要迭代 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());
    

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

【讨论】:

这是一个有趣的解决方案,但在我看来,它只能在 IterableFoo 和 Bar 定义在同一个 crate 中时才有效,对吧?所以你不能在你的 crate 定义的通用特征中使用它,你的 crate 的用户将在他们自己的实现中使用它......或者我错过了什么? @Pierre-Antoine 我添加了另一个允许非板条箱实施的解决方案。【参考方案3】:

将来,您将在您的一生中使用 want 和 associated type constructor 'a,但 Rust 还不支持。见RFC 1598

【讨论】:

以上是关于如何在关联类型中指定生命周期参数?的主要内容,如果未能解决你的问题,请参考以下文章

我啥时候需要在 Rust 中指定显式生命周期?

几种自定义Spring生命周期的初始化和销毁方法

SurfaceHolder 回调如何与 Activity 生命周期相关联?

Struts2-整理笔记Action生命周期如何获取参数(3种)集合类型参数封装

4android生命周期的介绍

没有为此构建指定目标。您必须指定一个有效的生命周期阶段