Почему добавление универсального типа к трейту влияет на время жизни трейт-объектов и связанных типов?

У меня есть следующий код:

trait T<GT> {
    type AT;

    fn foo(&self);
}

struct AbstractT<GT, AT> {
    t: Box<dyn T<GT, AT = AT>>,
}

impl<GT, AT> T<GT> for AbstractT<GT, AT> {
    type AT = AT;

    fn foo(&self) {
        self.t.foo();
    }
}

fn boxed_abstract<GT, TT: T<GT> + 'static>(tt: TT) -> Box<dyn T<GT, AT = TT::AT>> {
    Box::new(AbstractT { t: Box::new(tt) })
}

детская площадка

Что вызывает эти ошибки:

error[E0310]: the associated type `<TT as T<GT>>::AT` may not live long enough
  --> src/lib.rs:20:5
   |
20 |     Box::new(AbstractT { t: Box::new(tt) })
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `<TT as T<GT>>::AT: 'static`...
note: ...so that the type `AbstractT<GT, <TT as T<GT>>::AT>` will meet its required lifetime bounds
  --> src/lib.rs:20:5
   |
20 |     Box::new(AbstractT { t: Box::new(tt) })
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0310]: the parameter type `GT` may not live long enough
  --> src/lib.rs:20:5
   |
19 | fn boxed_abstract<GT, TT: T<GT> + 'static>(tt: TT) -> Box<dyn T<GT, AT = TT::AT>> {
   |                   -- help: consider adding an explicit lifetime bound...: `GT: 'static`
20 |     Box::new(AbstractT { t: Box::new(tt) })
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: ...so that the type `AbstractT<GT, <TT as T<GT>>::AT>` will meet its required lifetime bounds
  --> src/lib.rs:20:5
   |
20 |     Box::new(AbstractT { t: Box::new(tt) })
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Если я удалю GT отовсюду, он компилируется нормально, но с GT он терпит неудачу с кучей ошибок времени жизни. Кажется, что существование GT не должно влиять на время жизни ни dyn T, ни T::AT (потому что он в них не используется), но, очевидно, влияет. Точно так же время жизни dyn T не должно зависеть от времени жизни GT или AT, но очевидно, что это так.

Я что-то упускаю или это проблема вывода на всю жизнь?


person FLashM    schedule 19.05.2020    source источник


Ответы (1)


Возможно, вам что-то не хватает. Rust автоматически определяет границы времени жизни для объектов признаков, в случае упакованных в коробки объектов признаков автоматически выводятся границы времени жизни 'static. Например, вот что видит компилятор Rust, когда смотрит на ваш код:

trait T<GT> {
    type AT;

    fn foo(&self);
}

struct AbstractT<GT, AT> {
    // notice the added "+ 'static" below
    t: Box<dyn T<GT, AT = AT> + 'static>,
}

impl<GT, AT> T<GT> for AbstractT<GT, AT> {
    type AT = AT;

    fn foo(&self) {
        self.t.foo();
    }
}

// notice the added "+ 'static" in the return type
fn boxed_abstract<GT, TT: T<GT> + 'static>(tt: TT) -> Box<dyn T<GT, AT = TT::AT> + 'static> {
    Box::new(AbstractT { t: Box::new(tt) })
}

Чтобы он скомпилировался, нам просто нужно добавить более явные 'static границы для всех ваших общих типов, например:

trait T<GT> {
    type AT;

    fn foo(&self);
}

struct AbstractT<GT, AT> {
    t: Box<dyn T<GT, AT = AT>>,
}

impl<GT, AT> T<GT> for AbstractT<GT, AT> {
    type AT = AT;

    fn foo(&self) {
        self.t.foo();
    }
}

fn boxed_abstract<GT, TT>(tt: TT) -> Box<dyn T<GT, AT = TT::AT>>
    where TT: T<GT> + 'static, GT: 'static
{
    Box::new(AbstractT { t: Box::new(tt) })
}

детская площадка

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

Дальнейшее чтение:

Обновить

Если вам не нравится 'static требование, вы можете сделать AbstractT универсальным на время жизни, добавив явную аннотацию времени жизни к его типу, например:

trait T<GT> {
    type AT;

    fn foo(&self);
}

struct AbstractT<'a, GT, AT> {
    t: Box<dyn T<GT, AT = AT> + 'a>,
}

impl<'a, GT, AT> T<GT> for AbstractT<'a, GT, AT> {
    type AT = AT;

    fn foo(&self) {
        self.t.foo();
    }
}

fn boxed_abstract<'a, GT, TT>(tt: TT) -> Box<dyn T<GT, AT = TT::AT> + 'a>
    where TT: T<GT> + 'a, GT: 'a
{
    Box::new(AbstractT { t: Box::new(tt) })
}

детская площадка

person pretzelhammer    schedule 19.05.2020
comment
Что ж, Т не контейнер. Он не содержит ни GT, ни AT. Его срок службы не должен предъявлять требований к сроку службы GT или AT, верно? Я не хочу, чтобы GT или AT были статичными. А без GT ржавчина прекрасно понимает, что срок службы Т не зависит от срока службы АКПП. - person FLashM; 19.05.2020
comment
Итак, мой вопрос: 1. Почему добавление GT заставляет ржавчину думать, что время жизни T ограничено временем жизни AT? 2. Как обойти это, т.е. иметь T: 'static, а AT / GT - нет. - person FLashM; 19.05.2020
comment
@FLashM Вы правы, время жизни AT не должно ограничиваться временем жизни T, я обновил свой ответ, чтобы отразить это. Кроме того, я обновил свой ответ, включив в него версию решения без каких-либо зависимостей от static. - person pretzelhammer; 19.05.2020
comment
Если вы сохраните исходное ограничение TT: 'static, вам не нужно добавлять параметр в AbstractT, чтобы boxed_abstract работал: пример. Однако вы не сможете поместить возвращенный Box на другой уровень AbstractT, потому что для самого AbstractT требуется 'static. - person trentcl; 19.05.2020
comment
Хм. Интересно, что ограничение GT снижает требования к AT. Однако вопрос о GT все еще остается в силе. Время жизни GT не влияет на время жизни T, есть ли способ заставить boxed_abstract работать без ограничения времени жизни GT? - person FLashM; 20.05.2020