Таинственная проблема времени жизни при реализации черты для объекта dyn - PullRequest
0 голосов
/ 23 января 2019

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

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 Ответ

0 голосов
/ 23 января 2019

Типы объектов-черт имеют связанный срок жизни, но его можно опустить.Тип объекта с полным признаком пишется 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 не дает никакой ошибки, потому что его реализация не требует, чтобы время жизни двух объектов признаков было одинаковым.Почему partial_cmp заботится, хотя?

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

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

Эта черта требует, чтобы other был типа Self.Это означает, что когда partial_cmp вызывает cmp, он пытается передать &'b (dyn SimpleOrder + 'b) параметру, который ожидает &'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 позволяет заменять тип параметра одним из его супертипов при реализации метода черты (он делает метод строго более общим, даже если он явно немного использовался при проверке типов).


Чтобы сделать вашу реализацию как можно более общей, вы должны ввести параметр времени жизни в 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 {}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...