Как указать параметры времени жизни в связанном типе?
У меня есть эта черта и простая структура:
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
, чтобы это работало?
Ржавая детская площадка
Ответы
Ответ 1
Есть два решения вашей проблемы. Давайте начнем с самого простого:
Добавьте всю жизнь к вашей черте
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 тогда должна принимать свой аргумент по значению. В вашем случае вы будете реализовывать черту для ссылки:
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,
{}
но это не достаточно умны, пока.
В более сложных случаях вы можете использовать функцию Rust, которая еще не реализована: общие связанные типы (GAT). Работа для этого отслеживается в выпуске 44265.
Ответ 2
В будущем вы хотите связанный конструктор типов для вашей жизни 'a
, но Rust пока не поддерживает. См. RFC 1598
Ответ 3
Используйте тип обертки
Если признак и все его реализации определены в одном ящике, может быть полезен вспомогательный тип:
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
результат 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;
fn get(&self) -> &Self::Iterable;
}
Это требует, чтобы любой тип реализации содержал часть, которая может быть представлена таким образом:
impl Foo for Bar {
type Iterable = Vec<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
в обсуждаемом примере.