私はこの特性と単純な構造を持っています:
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
トレイトの実装をどのように変更する必要がありますか?
問題には2つの解決策があります。最も簡単なものから始めましょう:
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
を2回呼び出そうとすると、関数はコンパイルされません。
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,
{}
しかし、それは十分スマートではありません まだ 。
より複雑なケースでは、Rustまだ実装されていない機能を使用できます: Generic Associated Types (GATs)。その作業は追跡されています 問題44265 。
トレイトとそのすべての実装が1つのクレートで定義されている場合、ヘルパータイプが役立ちます。
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()
}
}
このソリューションでは、別のクレートでの実装は許可されません。もう1つの欠点は、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());
}
}
このソリューションは、特性定義クレートの外での実装を許可します。一般的な使用サイトでの拘束された作業は、前のソリューションと同じくらい厄介です。実装型には、使用サイトの境界が例のVec
およびIntoIterator
ほど容易に満たされない場合に、関連する型を提供するという唯一の目的を持つ内部Shell構造体が必要になる場合があります。議論した。
将来的には、 wantに関連付けられた型コンストラクター が一生あります'a
しかしRustはまだサポートしていません。 RFC 1598 を参照してください)