我有这个特点和简单的结构:
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
特质实施,以使其有效?
答案 0 :(得分:23)
您的问题有两种解决方案。让我们从最简单的一个开始:
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中跟踪该工作。
答案 1 :(得分:1)
将来,您want associated type constructor生命'a
,但Rust还不支持。见RFC 1598
答案 2 :(得分:0)
如果特征及其所有实现都在一个板条箱中定义,则辅助类型可能是有用的:
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
绑定编码到特征的定义中,因此需要将其指定为要迭代{的结果的通用代码的附加(更高级别)绑定{1}}:
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());
}
}
和Vec
那样容易满足时,实现类型可能需要内部shell结构,其唯一目的是提供关联的类型。