Я новичок в Rust из Python. Это мой 4-й день изучения Rust.

После моего первого вопроса Приведение типов для типа Option у меня есть следующий вопрос, связанный с синтаксис match и концепция владения.


Во-первых, я объявляю структуру ListNode с реализацией new.

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ListNode {
  pub val: i32,
}

impl ListNode {
  #[inline]
  fn new(val: i32) -> Self {
    ListNode {
      val
    }
  }
}

Моя цель - сравнить, одинаковы ли два узла, сравнивая значение узла. Вот моя уродливая реализация.

fn is_same(a: &Option<ListNode>, b: &Option<ListNode>) -> bool {
    if a == None && b == None { return true; }
    else if a == None && b != None { return false; }
    else if a != None && b == None { return false; }

    let ca: ListNode = a.clone().unwrap_or(ListNode::new(0));
    let cb: ListNode = b.clone().unwrap_or(ListNode::new(0));
    if ca.val == cb.val { return true; }
    else { return false; }
}

fn main() {
    let a: Option<ListNode> = Some(ListNode::new(0));
    let b: Option<ListNode> = Some(ListNode::new(0));
    
    println!("{:?}", is_same(&a, &b));
}

Потом у меня было много ошибок ...

no implementation for `&std::option::Option<ListNode> == std::option::Option<_>

Насколько мне известно о концепции владения, использование * для заимствованного параметра должно быть необязательным. Но, чтобы сравнение работало, мне нужно добавить *. Ниже доработанная функция работает. (Еще одно обнаруженное заключается в том, что использование a.clone() нормально, но *a.clone() неверно с ошибкой type `ListNode` cannot be dereferenced.)

fn is_same(a: &Option<ListNode>, b: &Option<ListNode>) -> bool {
    if *a == None && *b == None { return true; }
    else if *a == None && *b != None { return false; }
    else if *a != None && *b == None { return false; }

    let ca: ListNode = a.clone().unwrap_or(ListNode::new(0));
    let cb: ListNode = b.clone().unwrap_or(ListNode::new(0));
    if ca.val == cb.val { return true; }
    else { return false; }
}

Поскольку приведенный выше код решения слишком уродлив, вот еще одна реализация с использованием match без избыточных unwrap_or и *.

fn is_same(a: &Option<ListNode>, b: &Option<ListNode>) -> bool {
    match (a, b) {
        (None, None) => true,
        (None, _) => false,
        (_, None) => false,
        (Some(a), Some(b)) => a.val == b.val,
    }
}

Это работает отлично, a и b успешно сравниваются с None без * и unwrap_or.


Этот код заканчивается вопросами ниже:

  • Почему * нужно сравнивать с None в моем исходном коде?
  • В чем магия match? Как этот синтаксис заставляет код пропускать использование * и unwrap_or для сравнения?
2
Kir Chou 13 Июл 2020 в 15:13

1 ответ

Лучший ответ

Почему * нужно сравнивать с None в моем исходном коде?

Само по себе это не необходимо , но Eq реализовано только для идентичных типов, поэтому вам нужно сравнивать либо два Option<T>, либо два &Option<T>. Из этого вы можете увидеть, что сравнивает ваши узлы с &None тоже сработало бы.

В чем магия матча? Как этот синтаксис заставляет код пропускать использование * и unwrap_or для сравнения?

Match Ergonomics. Обычно match пытается автоматически добавлять ссылки на шаблоны, чтобы попытаться разрешить типы.

Также я не знаю, заметили ли вы, и просто хотите работать усерднее, но Option<T: Eq> реализует Eq, поэтому is_same(&a, &b) можно было просто написать a == b.

И еще кое-что, #[inline] обычно не рекомендуется, как правило, лучше избегать подсказок, если вы специально не посмотрели на сгенерированный вывод, а компилятор не откажется предоставить то, что вы ищете, в противном случае. Имейте в виду: код здесь настолько прост, что в режиме выпуска и даже при использовании сложной версии is_same с кучей разыменований llvm обнаруживает, что к чему, и просто загружает и форматирует константу true.

1
Masklinn 13 Июл 2020 в 13:04