我有这个特点和简单的结构:
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
特征实现,以使其工作?
您的问题有两种解决方案。让我们从最简单的一个开始:
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) {}
不是为您的类型实现特征,而是实现它以引用您的类型。这种特性从来不需要了解有关生命的任何事情。
然后,特征函数必须按值获取其参数。在您的情况下,您将实现特征作为参考:
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正在跟踪这方面的工作。
在未来,你将want为你的一生associated type constructor 'a
但Rust还不支持。见RFC 1598
如果特征及其所有实现都在一个包中定义,那么帮助器类型可能很有用:
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());
}
}
特征可以定义一种关联类型,该类型允许访问对象的一部分,该对象在引用中绑定,提供必要的访问特征。
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结构,其唯一目的是提供相关类型,以防使用站点边界不像所讨论的示例中的Vec
和IntoIterator
那样容易满足。