[英]How do I specify lifetime parameters in an associated type?
我有這個特點和簡單的結構:
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
特征實現才能使其工作?
您的問題有兩種解決方案。 讓我們從最簡單的開始:
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 以供參考:
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,
{}
但它不是足夠聰明尚未。
對於更復雜的情況,您可以使用尚未實現的 Rust 功能: 通用關聯類型(GAT)。 相關工作在issue 44265 中進行跟蹤。
如果 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()
}
}
此解決方案不允許在不同的板條箱中實現。 另一個缺點是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 可以定義一個關聯類型,該類型可以訪問綁定在引用中的對象的一部分,提供必要的訪問 trait。
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 定義包之外的實現。 通用網站上的綁定工作與之前的解決方案一樣令人討厭。 實現類型可能需要一個內部 shell 結構,其唯一目的是提供關聯類型,以防使用站點邊界不像討論的示例中的Vec
和IntoIterator
那樣容易滿足。
將來,您將需要一個關聯的類型構造函數,用於您的生命周期'a
但 Rust 尚不支持。 請參閱RFC 1598
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.