Как использовать нестатические типажные объекты со связанными типами?

У меня такой тип:

struct Wrap<T>(Vec<T>);

Я хочу реализовать std::ops::Index и возвращать ссылки на объекты признаков. Это была моя первая попытка (площадка):

use std::ops::Index;
use std::fmt::Display;


impl<T> Index<usize> for Wrap<T>
where
    T: Display
{
    type Output = Display;
    fn index(&self, index: usize) -> &Self::Output {
        &self.0[index]
    }
}

Это не работает и приводит к этой ошибке:

error[E0310]: the parameter type `T` may not live long enough
  --> src/main.rs:13:9
   |
7  | impl<T> Index<usize> for Wrap<T>
   |      - help: consider adding an explicit lifetime bound `T: 'static`...
...
13 |         &self.0[index]
   |         ^^^^^^^^^^^^^^
   |
note: ...so that the type `T` will meet its required lifetime bounds
  --> src/main.rs:13:9
   |
13 |         &self.0[index]
   |         ^^^^^^^^^^^^^^

Думаю, я знаю, почему это происходит: type Output = Display эквивалентно type Output = Display + 'static, поскольку каждый объект-признак имеет ограничение на время жизни, которое по умолчанию равно 'static.

Итак, теперь я могу просто добавить 'static, привязанный к моему параметру T, но я думаю, что это чрезмерно ограничено. Я могу легко реализовать такой метод, когда не использую связанный тип:

impl<T> Wrap<T>
where
    T: Display,
{
    fn my_index(&self, index: usize) -> &Display {
        &self.0[index]
    }
}

Никаких 'static привязок не требуется, потому что теперь сигнатура обессахаривает:

fn my_index<'a>(&'a self, index: usize) -> &'a Display + 'a

В этом есть смысл: объект-признак должен прожить не менее 'a. (площадка со всем кодом).


Но могу ли я сделать эту работу, используя связанные типы (например, с чертой Index)? У меня такое ощущение, что это может работать с общими связанными типами, но (а) я не уверен и (б) они еще не реализованы.


person Lukas Kalbertodt    schedule 28.05.2018    source источник
comment
Я не слишком разбираюсь в ржавчине, так что не рассчитывайте на это. Думаю, у вас ошибка struct Wrap<T>(Vec<T>);. Поскольку Vec не может сам сохранять какие-либо черты (они не имеют размера, как вы сказали), этот оператор станет незаконным. Я сделал рабочее решение с коробкой, но не думаю, что вы этого хотите. play.rust-lang.org/   -  person hellow    schedule 28.05.2018
comment
@hellow Спасибо! Однако, как вы уже догадались, для меня это не решает проблемы. Мне действительно нужно вернуть типажный объект, а не связанный с ним тип. Для тех, кому интересно, почему я этого хочу: я в основном хочу использовать Index в качестве объекта-признака для стирания некоторых типов. Как 2_. Это не работает с Index напрямую, но использование Index в моем вопросе сделало это немного проще.   -  person Lukas Kalbertodt    schedule 28.05.2018


Ответы (3)


Одна из попыток - прикрепить к импу целую жизнь:

// Note: won't work.

impl<'a, T> Index<usize> for Wrap<T>
where
    T: Display + 'a,
{
    type Output = Display + 'a;
    fn index(&self, index: usize) -> &Self::Output {
        &self.0[index]
    }
}

Однако компилятор не примет его, потому что 'a не используется.

error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates
 --> src/main.rs:7:6
  |
7 | impl<'a, T> Index<usize> for Wrap<T>
  |      ^^ unconstrained lifetime parameter

код ошибки E0207 предлагает несколько решений, но поскольку мы не можем изменить признак Index, единственное приемлемое решение - сделать Wrap захват этого неограниченного параметра времени жизни:

use std::ops::Index;
use std::fmt::Display;
use std::marker::PhantomData;

struct Wrap<'a, T>(Vec<T>, PhantomData<&'a ()>);
//          ^~             ^~~~~~~~~~~~~~~~~~~

impl<'a, T> Index<usize> for Wrap<'a, T>
where
    T: Display + 'a,
{
    type Output = Display + 'a;
    fn index(&self, index: usize) -> &Self::Output {
        &self.0[index]
    }
}

fn main() {
    let w = Wrap(vec!['a', 'b'], PhantomData);
    println!("{}", &w[0]); // prints "a"

    let s = "hi".to_string();
    let w = Wrap(vec![&s], PhantomData);
    println!("{}", &w[0]); // prints "hi"
}

(площадка)

Конечно, это изменит ваш API, и это дополнительное время жизни заразит повсюду ... Если это неприемлемо, вы можете либо

  • Не используйте черту Index, вместо этого введите свою собственную, чувствительную к продолжительности жизни черту (таким образом, пользователям нужно будет использовать w.my_index(i) вместо &w[i]); или
  • Установите Output = Display + 'static и исключите все переходные типы. Пользователям нужно будет клонировать или использовать Rc.
person kennytm    schedule 28.05.2018

Вернуть ссылку на T вместо того, чтобы возвращать ссылку на объект признака и позволить пользователю приводить к объекту признака, или просто позволить Rust неявно вывести из контекста, когда выполнять приведение:

use std::fmt::Display;
use std::ops::Index;

fn main() {
    let w1 = Wrap(vec!['I', 'b']);
    let s = "am".to_string();
    let w2 = Wrap(vec![&s]);
    let w3 = Wrap(vec![1, 2]);

    let mut trait_store: Vec<Box<Display>> = Vec::new();

    trait_store.push(Box::new(w1.index(0)));
    trait_store.push(Box::new(w2.index(0)));
    trait_store.push(Box::new(w3.index(0)));

    for el in trait_store {
        println!("{}", el);
    }
}

struct Wrap<T>(Vec<T>);

impl<T> Index<usize> for Wrap<T> {
    type Output = T;
    fn index(&self, index: usize) -> &Self::Output {
        &self.0[index]
    }
}
person attdona    schedule 28.05.2018
comment
Спасибо за Ваш ответ! К сожалению, у меня это не работает, потому что я хочу использовать трейт как объект трейта и стирать тип. Так можно было, например, хранить Vec<&Index<usize, Output = Display>>. - person Lukas Kalbertodt; 28.05.2018

Привет, я встретил ту же проблему, что и ты. Вроде &Index<usize, Output = Display>. Это не работает с Index напрямую, но использование Index в моем вопросе немного упростило его.

Я не выяснил, выпускает ли Rust какие-то связанные функции или нет. Но я придумываю довольно глупый способ выполнить свои требования.

Этот метод работает только в том случае, если структуры, реализующие признак, перечислимы. Предположим, у вас есть три структуры Index1, Index2, Index3, все они реализуют черту Index<usize, Output = Display>

Затем мы можем просто обернуть эти структуры

pub enum Indices{
    Index1(Index1),
    Index2(Index2),
    Index3(Index3),
}

А затем реализуйте черту для перечисления и всех его вариантов, для этого есть пример: ржавчина - Как мне реализовать трейт для перечисления и его соответствующих вариантов? - Переполнение стека

person Forsworn    schedule 23.01.2021