为什么 Rust 不在匹配模式中执行隐式 deref 强制?

Bil*_*lly 8 coercion dereference rust

在阅读了 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),
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码导致以下错误:

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

我能够通过使用“匹配保护”并明确取消对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),
    }
}
Run Code Online (Sandbox Code Playgroud)

鉴于我对隐式取消引用和Dereftrait 实现的了解Rc,我本以为我的第一次尝试会成功。为什么我必须在这个例子中明确取消引用?

Apl*_*123 10

首先,我们需要了解什么是取消引用强制。如果T解引用U并且x是 type 的值T,则:

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

方法解析的工作原理是当您在类型上调用方法时,它首先通过向类型添加任何内容,然后添加&,然后添加&mut,然后取消引用来检查方法。因此,在确定要调用哪个方法时x.method(),它会首先检查采用T, then &T, then &mut T, then U, then &U, then 的方法&mut U在此处阅读更多内容)。这并不适用于运营商。因此,==不会强制不同的类型,这就是您必须显式取消引用的原因。

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

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),
    }
}
Run Code Online (Sandbox Code Playgroud)

但以下成功:

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),
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么是这样?我们来看第一个例子:

next_list是 type &Rc<List>,所以它开始搜索一个.eq方法。它立即找到一个在PartialEq实现中定义的Rcwith signature fn eq(&self, other: &Rc<List>)。但是,在这种情况下other是类型List,不能强制为&Rc<List>

那为什么第二个工作呢? Nil是 type List,所以它开始搜索一个.eq方法。它找不到任何 for List,所以它&List接下来尝试,在那里它找到PartialEq带有签名的派生实现fn eq(&self, other: &List)。在这种情况下, other 是 type &Rc<List>&List由于它的Deref实现,可以强制转换为。这意味着所有类型检查都正确并且代码可以正常工作。

至于为什么你的第一次尝试没有成功,它似乎不是 Rust 中的一个功能,并且有一个可以追溯到 2017 的建议添加它