【问题标题】: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

【问题讨论】:

    标签: rust lifetime


    【解决方案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】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-04-18
      • 2018-02-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-10
      • 2019-04-08
      相关资源
      最近更新 更多