Почему тело функции компилируется в структуре, но не в черте?

Этот код определяет очень простой признак для представления двоичных деревьев и структуры, реализующей этот признак:

pub trait BTree<T> {
    fn all(&self) -> Option<(&Self, &Self, &T)>;
    fn left(&self) -> Option<&Self>;
    fn right(&self) -> Option<&Self>;
    fn value(&self) -> Option<&T>;
}

pub struct MyBTree<T> {
    opt: Option<Box<(MyBTree<T>, MyBTree<T>, T)>>,
}

impl<T> BTree<T> for MyBTree<T> {
    fn all(&self) -> Option<(&Self, &Self, &T)> {
        match self.opt {
            None => None,
            Some(ref tuple) => Some((&tuple.0, &tuple.1, &tuple.2)),
        }
    }

    fn left(&self) -> Option<&Self> {
        match self.all() {
            None => None,
            Some((left, _, _)) => Some(left),
        }
    }

    fn right(&self) -> Option<&Self> {
        match self.all() {
            None => None,
            Some((right, _, _)) => Some(right),
        }
    }

    fn value(&self) -> Option<&T> {
        match self.all() {
            None => None,
            Some((_, _, value)) => Some(value),
        }
    }
}

Реализации left, right и value могут перемещаться внутри признака, поскольку они зависят только от метода all, определенного этим признаком, а не от деталей реализации.

Это отлично работает с value, но не с left и right. Например, если я попытаюсь переместить реализацию left в тело признака, я получаю следующую ошибку компиляции:

error[E0311]: the parameter type `T` may not live long enough
--> src/lib.rs:6:24
  |
6 |             match self.all() {
  |                        ^^^
  |
= help: consider adding an explicit lifetime bound for `T`
note: the parameter type `T` must be valid for the anonymous lifetime #1 defined on the method body at 5:9...
--> src/lib.rs:5:9
  |
5 | /         fn left(&self) -> Option<&Self> {
6 | |             match self.all() {
7 | |                 None => None,
8 | |                 Some((left, _, _)) => Some(left),
9 | |             }
10| |         }
  | |_________^
note: ...so that the reference type `&T` does not outlive the data it points at
--> src/lib.rs:6:24
  |
6 |             match self.all() {
  |

Почему эта проблема возникает в признаке, но не в реализации для MyBTree?

Почему компилятор жалуется на время жизни T в методах, которые игнорируют значение T - в то время как он работает с методом value, который упоминает T в его возвращаемом типе?

Ответ 1

Как эта проблема возникает в признаке, но не в ее реализации для MyBTree?

Эти сигнатуры методов становятся более нюансированными, если вы рассматриваете реализацию BTree<T> для типа, имеющего время жизни. Мой общий совет для всех ошибок времени жизни, связанных с типичным параметром типа или типом Self, является: сосредоточиться на случае, когда тип является заимствованным типом.

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

fn f<'a, 'b>() {
    // error[E0491]: in type `&'a &'b ()`, reference has a longer
    // lifetime than the data it references
    let _: &'a &'b ();
}

Ржавчина заставляет нас гарантировать, что данные, на которые ссылается ссылка, вызывают ссылку, в этом случае 'b переживает 'a.

fn f<'a, 'b: 'a>() {
    let _: &'a &'b ();
}

Теперь примените это к вашей ситуации BTree, если подумать, что не так, если T является заимствованным типом типа &(). Сначала рассмотрим следующие два метода, которые вы разместили в impl<T> BTree<T> for MyBTree<T>. Я написал конкретные сроки жизни, чтобы уточнить обсуждение.

impl<T> BTree<T> for MyBTree<T> {
    fn left<'a>(&'a self) -> Option<&'a Self> { /* ... */ }
    fn value<'a>(&'a self) -> Option<&'a T> { /* ... */ }
}

Чтобы вызывающий вызывал left, они должны знать, что Self переживает время жизни 'a. И чтобы вызывающий вызывал value, они должны знать, что Self переживает время жизни 'a и T переживает время жизни 'a (для того, чтобы &'a T будет значимым типом, как мы видели выше). Контроллер заимствования не позволит им вызывать эти методы, если эти требования не выполняются, и поэтому реализация может предполагать выполнение этих требований.

Кроме того, средство проверки заемщика может видеть, что , если Self переживает 'a , тогда также T переживает 'a, потому что MyBTree<T> содержит значение тип T.

Вот почему не было проблем с реализацией left и value внутри impl<T> BTree<T> for MyBTree<T>. Соединитель и структура MyBTree<T> вместе гарантируют, что все будет работать до тех пор, пока нам нужно.

Теперь, когда мы использовали эти методы в определении определения BTree<T>.

trait BTree<T> {
    fn left<'a>(&'a self) -> Option<&'a Self> { /* ... */ }
    fn value<'a>(&'a self) -> Option<&'a T> { /* ... */ }
}

Дела идут неправильно, потому что если вызывающий вызывает left, они должны знать, что Self переживает 'a, но они не гарантировали, что T переживания 'a. Например, они могут иметь T=&'b () для некоторого полностью несвязанного более короткого времени жизни 'b. Как мы видели выше, это сделало бы &'a T равным &'a &'b (), который не был бы юридическим типом.

Причина, по которой Rust довольна value, определяемой в этом признаке, заключается в том, что вызывающий абонент гарантирует, что Self и T переживают время жизни 'a. Причина, по которой Rust недовольна тем, что left, определенный в этом признаке, заключается в том, что вызывающий абонент гарантирует, что только Self переживает 'a, а не T переживает 'a, который предполагает реализация.

И почему компилятор жалуется на время жизни T в методах, которые игнорируют значение T - пока он работает с методом value, который упоминает T в его возвращаемом типе?

Ну, ошибка заключается не в возвращаемом значении, а в вызове all(). Посмотрите внимательно.

error[E0311]: the parameter type `T` may not live long enough
--> src/lib.rs:6:24
  |
6 |             match self.all() {
  |                        ^^^

Для вызова all() вызывающий абонент отвечает за доказательство того, что типы ввода и вывода являются допустимыми. Но в случае, если T что-то вроде &'b (), это может быть неверно. all() вернет &'a &'b (), поэтому средство проверки заемных средств предотвратит вызов.

Мы можем исправить это, сделав явные гарантии, которые предполагает наша реализация, в этом случае T переживает 'a.

trait BTree<T> {
    fn left<'a>(&'a self) -> Option<&'a Self>
    where
        T: 'a,
    { 
        /* ... */ 
    }
}