为什么 Rust 不在匹配模式中执行隐式取消引用强制?

Why does Rust not perform implicit deref coercion in match patterns?

阅读 Rust 书中关于 Smart Pointers and Interior mutability 的部分后,作为个人练习,我尝试编写一个函数来遍历智能指针的链表和 return “最后一个” " 列表中的元素:

#[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),
    }
}

鉴于我对隐式取消引用和 RcDeref 特性实现的了解,我本以为我的第一次尝试会成功。为什么我必须在此示例中显式取消引用?

首先,我们需要了解什么是解引用强制。如果 T 解引用到 U 并且 x 是类型 T 的值,那么:

  • *x*Deref::deref(&x)
  • &T 可以强制转换为 &U
  • x.method() 将在方法解析期间检查类型 U

方法解析的工作原理是,当您在类型上调用方法时,它首先通过不向类型添加任何内容来检查方法,然后添加 &,然后添加 &mut,然后取消引用.因此,在确定为 x.method() 调用哪个方法时,它将首先检查采用 T 的方法,然后是 &T,然后是 &mut T,然后是 [=13] =],然后是 &U,然后是 &mut U (read more here)。 不适用于运算符。因此,== 不会强制转换不同的类型,这就是为什么您必须显式取消引用的原因。

但是如果我们确实在 PartialEq 特征中使用了一个方法,比如 .eq 呢?事情变得有趣了。以下代码失败:

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,在那里它找到带有签名 fn eq(&self, other: &List) 的派生 PartialEq 实现。在这种情况下,other 是 &Rc<List> 类型,由于它的 Deref 实现,它可以被强制转换为 &List。这意味着所有内容都可以正确进行类型检查并且代码可以正常工作。

至于为什么你的第一次尝试没有成功,这似乎不是 Rust 的一个特性,而是 a proposal to add it dating back to 2017