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

qhhrdooz  于 2023-01-09  发布在  其他
关注(0)|答案(2)|浏览(97)

我在阅读这篇post时遇到了这种奇怪的行为,这篇文章的核心问题是当您匹配(&k, &v) = &(&String, &String)kv时,将得到类型String
为了弄清楚到底发生了什么,我写了下面的测试代码,结果让我更加震惊和困惑:
Playground Link

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
}

添加到行尾的ab I的类型注解由rust-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.在代码片段1/2/3中,为什么ab总是具有相同的类型,即使RHS具有不同的类型(x/ref_to_x/ref_ref_to_x ..)?
1.这种匹配是如何进行的(最好是逐步匹配过程)?
1.在编写代码时,如何获得与rust-analyzer/rustc完全相同的类型推断?
顺便说一句,这和rfc 2005 match-ergonomics有关吗?我在谷歌上搜索了很多,发现很多人在回答中提到了这一点。

ubby3x7f

ubby3x7f1#

是的。你所看到的是符合人体工程学的行为,而它们的行为可能不是你所期望的。
匹配人机工程学的工作方式是使用 * 绑定模式 *。有三种绑定模式可用,即使没有匹配人机工程学也可以使用:

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

该过程的工作原理如下:编译器从外向内处理模式,从绑定模式move开始。
每当编译器需要将一个 * 非引用模式 *(文本、结构体、元组、切片)与一个引用进行匹配时,它都会自动解除引用该引用并更新绑定模式:当&引用与匹配时,我们将得到ref绑定模式,对于&mut引用,如果当前绑定模式是refref mut,我们将得到ref,然后重复此过程,直到不再有引用为止。
如果我们匹配的是一个 * reference pattern *(绑定、通配符、const的引用类型或&/&mut模式),默认的绑定模式会重置回move
绑定变量时,编译器查看当前绑定模式:对于move,它将按原样匹配类型。对于refref mut,它将分别添加&&mut但只有一个
让我们逐行地学习您的示例。

let (a, b) = x;                // type of a: &&String, type of b: &&String

我们将一个 * non-reference pattern *(一个元组模式)与一个引用(&(&String, &String)类型)进行匹配,因此我们解引用该引用并将绑定模式设置为ref
现在我们有了一个元组模式来匹配类型为(&String, &String)的元组和绑定模式ref,我们将a&String进行匹配:这是一个引用模式(绑定),所以我们不改变绑定模式,但是,我们已经有了一个绑定模式ref,我们匹配的类型是&Stringref意味着我们添加了一个引用,所以我们以&&String结束,b也发生了完全相同的事情。

let (a, b) = ref_to_x;         // type of a: &&String, type of b: &&String

这里,就像前面的例子一样,我们将一个非引用模式(元组模式)与一个引用(&&(&String, &String))进行匹配,因此我们取消引用并将绑定模式设置为ref,但我们仍然有一个引用:&(&String, &String)。所以我们再次解引用。绑定模式已经是ref,所以我们不需要碰它。我们以匹配(a, b)(&String, &String)结束。这意味着a = &Stringb = &String。但是记住我们使用的是ref绑定模式,所以我们应该添加一个引用。我们只添加一个引用,* * 即使我们的对手是两个!**最后,我们有a = &&Stringb = &&String
此代码段中的其余示例以相同的方式工作。

let &(a, b) = ref_to_x;        // type of a: &&String, type of b: &&String

在这里,我们首先将&模式与&&(&String, &String)类型的引用进行匹配。这将删除两个引用,从而使我们将(a, b)&(&String, &String)进行匹配。从现在开始,我们将像第一个示例一样继续。
此代码片段中的其余示例类似。

let (&a, &b) = x;               // type of a: String, type of b: String

这是最有趣的一个例子。还记得我们是怎么讨论参考模式和非参考模式的吗?在这个例子中,这个事实起着至关重要的作用。
首先我们将元组模式与类型&(&String, &String)进行匹配,然后解引用元组并设置binding_mode = ref,现在我们匹配元组:我们需要将&a&b分别与&String进行匹配,绑定模式设置为ref
当我们将&a&String进行匹配时会发生什么?好吧,记住&是一个引用模式,当匹配引用模式时,我们完全忽略绑定模式。因此,我们将&a&String进行匹配,绑定模式重置为move。这将从两侧删除引用。剩下的是a = String&b也是一样。
此代码片段中的下一个示例是相同的。

hmae6n7t

hmae6n7t2#

这被称为"解构",通常用于模式匹配,如if let Some(val) = option
基本上,这是:

let x: (&String, &String) = (&String::new(), &String::new());
let (&a, &b) = x; // a: String, b: String

相当于:

let (&a, &b) = (&String::new(), &String::new()); // a: String, b: String

它相当于:

let (a, b) = (String::new(), String::new());

为了解释更多,我将使用一个更简单的例子。

let value: String = String::new();
let &also_value = &value; // also_value: String

重构的工作方式是"简化"两端。在这种情况下,它将取消对两端的引用,从而导致

let also_value = value;

这也可以通过间接方式实现,如果我们在中间添加一个步骤:

let value: String = String::new();
let ref_to_value: &String = &value;
let &also_value = ref_to_value; // also_value: String

它可以看穿间接寻址,现在将解引用ref_to_value以再次获得String
匹配人机工程学也可能涉及,但仅用于解引用整个元组。
注意:要在重构赋值中获取引用,可以使用ref

let value: String = String::new();
let ref ref_to_value = value; // ref_to_value: &String

通常这只用于模式匹配,如:

if let Some(ref x) = option { ... }

相关问题