Почему Rust не выполняет неявное приведение ссылки в шаблонах соответствия? - PullRequest
6 голосов
/ 11 июля 2020

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

#[derive(Debug, PartialEq)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn get_last(list: &List) -> &List {
    match list {
        Nil | Cons(_, Nil) => list,
        Cons(_, next_list) => get_last(next_list),
    }
}

Этот код приводит к следующей ошибке:

   |         Nil | Cons(_, Nil) => list,
   |                       ^^^ expected struct `std::rc::Rc`, found enum `List

Мне удалось заставить его работать с помощью« защиты совпадений »и явного разыменования в шаблоне Cons(_, x):

fn get_last(list: &List) -> &List {
    match list {
        Nil => list,
        Cons(_, next_list) if **next_list == Nil => list,
        Cons(_, next_list) => get_last(next_list),
    }
}

Учитывая то, что я узнал о неявном разыменовании и реализации черты Deref для Rc, я ожидал, что моя первая попытка сработает. Почему я должен явно разыменовать в этом примере?

1 Ответ

7 голосов
/ 19 июля 2020

Во-первых, нам нужно понять, что такое deref coercion. Если T заменяется на U и x является значением типа T, то:

  • *x равно *Deref::deref(&x)
  • &T может быть приведенным к &U
  • x.method() будет проверять тип U во время разрешения метода.

Как работает разрешение метода, когда вы вызываете метод для типа, он сначала проверяет метод, ничего не добавляя к типу, затем добавляя &, затем добавляя &mut, а затем разыменовывая. Итак, когда выясняется, какой метод вызвать для x.method(), он сначала проверит метод, который принимает T, затем &T, затем &mut T, затем U, затем &U, затем &mut U ( подробнее здесь ). Этот не применяется к операторам. Следовательно, == не будет приводить различные типы, поэтому вы должны явно разыменовать.

Но что, если бы мы действительно использовали метод, например .eq в трейте PartialEq? Все становится интересно. Следующий код не работает:

fn get_last(list: &List) -> &List {
    match list {
        Nil => list,
        Cons(_, next_list) if next_list.eq(Nil) => list,
        Cons(_, next_list) => get_last(next_list),
    }
}

, но следующий успешно:

fn get_last(list: &List) -> &List {
    match list {
        Nil => list,
        // notice how it's Nil.eq and not next_list.eq
        Cons(_, next_list) if Nil.eq(next_list) => list,
        Cons(_, next_list) => get_last(next_list),
    }
}

Почему это? Давайте посмотрим на первый пример:

next_list имеет тип &Rc<List>, поэтому он начинает поиск метода .eq. Он немедленно находит определенную в реализации PartialEq для Rc с подписью fn eq(&self, other: &Rc<List>). Однако в данном случае other имеет тип List, который не может быть приведен к &Rc<List>.

Тогда почему работает второй? Nil имеет тип List, поэтому начинает поиск метода .eq. Он не может найти ничего для List, поэтому затем пытается &List, где находит производную имплантацию PartialEq с подписью fn eq(&self, other: &List). В этом случае other имеет тип &Rc<List>, который может быть приведен к &List из-за его реализации Deref. Это означает, что все типы проверяются правильно, и код работает. назад в 2017 .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...