Таинственная проблема времени жизни при реализации черты для объекта dyn

Рассмотрим следующий игрушечный пример:

use std::cmp::Ordering;

pub trait SimpleOrder {
    fn key(&self) -> u32;
}

impl PartialOrd for dyn SimpleOrder {
    fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for dyn SimpleOrder {
    fn cmp(&self, other: &dyn SimpleOrder) -> Ordering {
        self.key().cmp(&other.key())
    }
}

impl PartialEq for dyn SimpleOrder {
    fn eq(&self, other: &dyn SimpleOrder) -> bool {
        self.key() == other.key()
    }
}

impl Eq for SimpleOrder {}

Это не компилируется. Он утверждает, что в реализации для partial_cmp существует проблема времени жизни:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
 --> src/main.rs:9:23
  |
9 |         Some(self.cmp(other))
  |                       ^^^^^
  |
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the method body at 8:5...
 --> src/main.rs:8:5
  |
8 | /     fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
9 | |         Some(self.cmp(other))
10| |     }
  | |_____^
note: ...so that the declared lifetime parameter bounds are satisfied
 --> src/main.rs:9:23
  |
9 |         Some(self.cmp(other))
  |                       ^^^^^
  = note: but, the lifetime must be valid for the static lifetime...
  = note: ...so that the types are compatible:
          expected std::cmp::Eq
             found std::cmp::Eq

Я действительно не понимаю эту ошибку. В частности, "ожидаемый std::cmp::Eq найденный std::cmp::Eq " вызывает недоумение.

Если я встраиваю вызов вручную, он прекрасно компилируется:

fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
    Some(self.key().cmp(&other.key()))
}

Что здесь происходит?

Ответ 1

Типы объектов-черт имеют связанный срок жизни, но его можно опустить. Тип объекта с полным признаком пишется dyn Trait + 'a (когда за ссылкой стоит добавить круглые скобки: &(dyn Trait + 'a)).

Сложность состоит в том, что когда ограничение на время жизни опущено, правила немного усложняются.

Во-первых, мы имеем:

impl PartialOrd for dyn SimpleOrder {

Здесь компилятор выводит + 'static. Параметры времени жизни никогда не вводятся в блоках impl (по состоянию на Rust 1.32.0).

Далее имеем:

    fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {

Предполагается, что тип other равен &'b (dyn SimpleOrder + 'b), где 'b - это неявный параметр времени жизни, введенный в partial_cmp.

    fn partial_cmp<'a, 'b>(&'a self, other: &'b (dyn SimpleOrder + 'b)) -> Option<Ordering> {

Итак, теперь у нас есть, что self имеет тип &'a (dyn SimpleOrder + 'static) то время как other имеют тип &'b (dyn SimpleOrder + 'b). В чем проблема?

Действительно, cmp не дает никакой ошибки, потому что его реализация не требует, чтобы время жизни двух объектов trait было одинаковым. Почему partial_cmp заботится, хотя?

Потому что partial_cmp вызывает Ord::cmp. Когда тип проверяет вызов метода черты, компилятор проверяет сигнатуру черты. Давайте рассмотрим эту подпись:

pub trait Ord: Eq + PartialOrd<Self> {
    fn cmp(&self, other: &Self) -> Ordering;

Эта черта требует, чтобы other были типа " Self. Это означает, что когда partial_cmp вызывает cmp, он пытается передать a &'b (dyn SimpleOrder + 'b) параметру, который ожидает &'b (dyn SimpleOrder + 'static) a &'b (dyn SimpleOrder + 'static), потому что Self является dyn SimpleOrder + 'static. Это преобразование недопустимо ('b не может быть преобразовано в 'static), поэтому компилятор выдает ошибку.

Итак, почему допустимо устанавливать тип other на &'b (dyn SimpleOrder + 'b) при реализации Ord? Поскольку &'b (dyn SimpleOrder + 'b) является супертипом &'b (dyn SimpleOrder + 'static), а Rust позволяет заменять тип параметра одним из его супертипов при реализации метода trait (это делает метод строго более общий, хотя он явно не используется при проверке типов).


Чтобы сделать вашу реализацию как можно более универсальной, вы должны ввести параметр времени жизни для impl s:

use std::cmp::Ordering;

pub trait SimpleOrder {
    fn key(&self) -> u32;
}

impl<'a> PartialOrd for dyn SimpleOrder + 'a {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl<'a> Ord for dyn SimpleOrder + 'a {
    fn cmp(&self, other: &Self) -> Ordering {
        self.key().cmp(&other.key())
    }
}

impl<'a> PartialEq for dyn SimpleOrder + 'a {
    fn eq(&self, other: &Self) -> bool {
        self.key() == other.key()
    }
}

impl<'a> Eq for dyn SimpleOrder + 'a {}