模式匹配引用时的奇怪类型

Ste*_*Lau 5 types type-inference pattern-matching rust match-ergonomics

我在阅读这篇文章时遇到了这种奇怪的行为,这篇文章的核心问题是当你匹配时(&k, &v) = &(&String, &String),k会v得到类型String。

为了弄清楚发生了什么,我编写了以下测试代码,结果让我更加震惊和困惑:

游乐场链接

fn main() {
    let x: &(&String, &String) = &(&String::new(), &String::new());
    let ref_to_x: &&(&String, &String) = &x;
    let ref_ref_to_x: &&&(&String, &String) = &&x;
    let ref_ref_ref_to_x: &&&&(&String, &String) = &&&x;
    
    // code snippet 1
    let (a, b) = x;                // type of a: &&String, type of b: &&String
    let (a, b) = ref_to_x;         // type of a: &&String, type of b: &&String
    let (a, b) = ref_ref_to_x;     // type of a: &&String, type of b: &&String
    let (a, b) = ref_ref_ref_to_x; // type of a: &&String, type of b: &&String

    // code snippet 2
    let &(a, b) = x;                // type of a: &String, type of b: &String
    let &(a, b) = ref_to_x;        // type of a: &&String, type of b: &&String
    let &(a, b) = ref_ref_to_x;    // type of a: &&String, type of b: &&String
    let &(a, b) = ref_ref_ref_to_x;// type of a: &&String, type of b: &&String

    // code snippet 3
    let (&a, &b) = x;               // type of a: String, type of b: String
    let (&a, &b) = ref_to_x;        // type of a: String, type of b: String
    let (&a, &b) = ref_ref_to_x;    // type of a: String, type of b: String
    let (&a, &b) = ref_ref_ref_to_x;// type of a: String, type of b: String
}
Run Code Online (Sandbox Code Playgroud)

我添加到行尾的a和 的类型注释是由 推断出来的。brust-analyzer

请注意,由于错误而code snippet 3 无法编译,但我认为这并不重要(也许我在这里错了,如果是这样,请指出我,谢谢)因为我们专注于和 的can not move out of xx because it's borrowrd/can not move out of xx which is behind a shared reference类型。ab

我的问题是:

  1. 为什么a即使bRHS 在代码片段 1/2/3 中具有不同的类型(x/ref_to_x/ref_ref_to_x..),但总是具有相同的类型?
  2. 这种匹配是如何发生的(如果有逐步的匹配过程将不胜感激)?
  3. 如何获得与rust-analyzer/rustc编写代码时完全相同的类型推断?

顺便说一句,这与rfc 2005 比赛人体工程学相关吗?我用谷歌搜索了很多,发现很多人在他们的答案中提到了这一点。

Cha*_*man 7

是的。您所看到的是匹配人体工程学的实际效果,但它们的行为可能不是您所期望的。

匹配人体工程学的工作方式是使用绑定模式。共有三种绑定模式可供选择,即使没有符合人体工程学的情况也可以使用:

  • 移动。这是引入匹配人体工程学之前的默认绑定模式,并且它总是尝试移动(或复制)该值。
  • ref。这就是当您将ref运算符应用于绑定时所得到的结果(令人惊讶),并且它添加了一个引用。例如,在 中match e { ref r => ... },r是&e。
  • ref mut。与 类似ref,但使用可变借用(并使用ref mut运算符指定)。

该过程的工作原理如下:编译器从外向内处理模式。move该过程从绑定模式开始。

每次编译器需要将非引用模式(文字、结构、元组、切片)与引用进行匹配时,它都会自动取消引用引用并更新绑定模式:当&引用与引用匹配时,我们将获得ref绑定模式,并且作为&mut参考,我们将获得ref当前绑定模式是否为ref或否则ref mut。然后重复这个过程,直到我们不再有参考为止。

如果我们根据引用模式(绑定、通配符、const引用类型或&/&mut模式)进行匹配,则默认绑定模式将重置回move.

当绑定变量时,编译器会查看当前的绑定模式:对于move,它将按原样匹配类型。对于ref和ref mut,它将分别添加&或&mut。但只有一个。

让我们逐行跟随您的示例。

let (a, b) = x;                // type of a: &&String, type of b: &&String
Run Code Online (Sandbox Code Playgroud)

我们将非引用模式(元组模式)与引用(类型为&(&String, &String))进行匹配。所以我们取消引用引用并将绑定模式设置为ref。

现在我们得到了一个元组模式来匹配 type 的元组(&String, &String)和ref. 我们匹配a:&String它是一个参考模式(绑定),所以我们不改变绑定模式。然而,我们已经有了ref. 我们匹配的类型是&String,ref意味着我们添加了一个引用,所以我们以 结尾&&String。完全相同的事情也发生在b。

let (a, b) = ref_to_x;         // type of a: &&String, type of b: &&String
Run Code Online (Sandbox Code Playgroud)

在这里,就像前面的示例一样,我们将非引用模式(元组模式)与引用 ( &&(&String, &String)) 进行匹配。因此我们取消引用并将绑定模式设置为ref。但我们还是有一个参考:&(&String, &String)。所以我们再次取消引用。绑定模式已经有了ref,所以我们不需要碰它。(a, b)我们以匹配结束(&String, &String)。这意味着a = &String,b = &String。但请记住我们使用的是ref绑定模式,因此我们应该添加引用。即使我们匹配了两个引用,我们也只添加了一个引用!最后,我们有,。a = &&Stringb = &&String

此代码片段中的其余示例的工作方式相同。

let &(a, b) = ref_to_x;        // type of a: &&String, type of b: &&String
Run Code Online (Sandbox Code Playgroud)

在这里,我们首先将&模式与 type 的引用进行匹配&&(&String, &String)。这会删除两个引用,使我们能够(a, b)匹配&(&String, &String). 从现在开始,我们继续像第一个例子一样。

此代码片段中的其余示例类似。

let (&a, &b) = x;               // type of a: String, type of b: String
Run Code Online (Sandbox Code Playgroud)

这是最有趣的一个。还记得我们如何讨论参考模式与非参考模式吗?在这个例子中,事实起着至关重要的作用。

首先,我们将元组模式与类型进行匹配&(&String, &String)。我们取消引用 tuple 和 set binding_mode = ref。现在我们匹配元组:我们必须匹配&a和&b每个反对&String,绑定模式设置为ref。

&a当我们匹配时会发生什么&String?好吧,记住&是一个参考模式,当匹配参考模式时我们完全忽略绑定模式。所以我们匹配&a,&String绑定模式重置为move。这会删除双方的引用,留下a = String. 对于 也一样&b。

此代码片段中的下一个示例是相同的。