如何在关联类型中指定生命周期参数?
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
实际上是&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
,只有&'a T: Foo
的第二种方法中如何引用关联类型T::Item
吗?编辑:我找到了!这是<&T as Foo>::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 结构,其唯一目的是提供关联类型,以防使用站点边界不像所讨论的示例中的 Vec
和 IntoIterator
那样容易满足。
【讨论】:
这是一个有趣的解决方案,但在我看来,它只能在 IterableFoo 和 Bar 定义在同一个 crate 中时才有效,对吧?所以你不能在你的 crate 定义的通用特征中使用它,你的 crate 的用户将在他们自己的实现中使用它......或者我错过了什么? @Pierre-Antoine 我添加了另一个允许非板条箱实施的解决方案。【参考方案3】:将来,您将在您的一生中使用 want 和 associated type constructor 'a
,但 Rust 还不支持。见RFC 1598
【讨论】:
以上是关于如何在关联类型中指定生命周期参数?的主要内容,如果未能解决你的问题,请参考以下文章
SurfaceHolder 回调如何与 Activity 生命周期相关联?