我在阅读这篇post时遇到了这种奇怪的行为,这篇文章的核心问题是当您匹配(&k, &v) = &(&String, &String)
、k
和v
时,将得到类型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
}
添加到行尾的a
和b
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
,但我认为这并不重要(也许我在这里错了,如果是这样,指出我,谢谢),因为我们集中在a
和b
的类型。
我的问题是:
1.在代码片段1/2/3中,为什么a
和b
总是具有相同的类型,即使RHS具有不同的类型(x/ref_to_x/ref_ref_to_x ..)?
1.这种匹配是如何进行的(最好是逐步匹配过程)?
1.在编写代码时,如何获得与rust-analyzer/rustc
完全相同的类型推断?
顺便说一句,这和rfc 2005 match-ergonomics有关吗?我在谷歌上搜索了很多,发现很多人在回答中提到了这一点。
2条答案
按热度按时间ubby3x7f1#
是的。你所看到的是符合人体工程学的行为,而它们的行为可能不是你所期望的。
匹配人机工程学的工作方式是使用 * 绑定模式 *。有三种绑定模式可用,即使没有匹配人机工程学也可以使用:
ref
。当你把ref
操作符应用到一个绑定时,你会得到这样的结果(令人惊讶),它会添加一个引用。例如,在match e { ref r => ... }
中,r
是&e
。ref mut
。与ref
类似,但使用可变借位(并使用ref mut
运算符指定)。该过程的工作原理如下:编译器从外向内处理模式,从绑定模式
move
开始。每当编译器需要将一个 * 非引用模式 *(文本、结构体、元组、切片)与一个引用进行匹配时,它都会自动解除引用该引用并更新绑定模式:当
&
引用与匹配时,我们将得到ref
绑定模式,对于&mut
引用,如果当前绑定模式是ref
或ref mut
,我们将得到ref
,然后重复此过程,直到不再有引用为止。如果我们匹配的是一个 * reference pattern *(绑定、通配符、
const
的引用类型或&
/&mut
模式),默认的绑定模式会重置回move
。绑定变量时,编译器查看当前绑定模式:对于
move
,它将按原样匹配类型。对于ref
和ref mut
,它将分别添加&
或&mut
。但只有一个。让我们逐行地学习您的示例。
我们将一个 * non-reference pattern *(一个元组模式)与一个引用(
&(&String, &String)
类型)进行匹配,因此我们解引用该引用并将绑定模式设置为ref
。现在我们有了一个元组模式来匹配类型为
(&String, &String)
的元组和绑定模式ref
,我们将a
与&String
进行匹配:这是一个引用模式(绑定),所以我们不改变绑定模式,但是,我们已经有了一个绑定模式ref
,我们匹配的类型是&String
,ref
意味着我们添加了一个引用,所以我们以&&String
结束,b
也发生了完全相同的事情。这里,就像前面的例子一样,我们将一个非引用模式(元组模式)与一个引用(
&&(&String, &String)
)进行匹配,因此我们取消引用并将绑定模式设置为ref
,但我们仍然有一个引用:&(&String, &String)
。所以我们再次解引用。绑定模式已经是ref
,所以我们不需要碰它。我们以匹配(a, b)
和(&String, &String)
结束。这意味着a = &String
,b = &String
。但是记住我们使用的是ref
绑定模式,所以我们应该添加一个引用。我们只添加一个引用,* * 即使我们的对手是两个!**最后,我们有a = &&String
和b = &&String
。此代码段中的其余示例以相同的方式工作。
在这里,我们首先将
&
模式与&&(&String, &String)
类型的引用进行匹配。这将删除两个引用,从而使我们将(a, b)
与&(&String, &String)
进行匹配。从现在开始,我们将像第一个示例一样继续。此代码片段中的其余示例类似。
这是最有趣的一个例子。还记得我们是怎么讨论参考模式和非参考模式的吗?在这个例子中,这个事实起着至关重要的作用。
首先我们将元组模式与类型
&(&String, &String)
进行匹配,然后解引用元组并设置binding_mode = ref
,现在我们匹配元组:我们需要将&a
和&b
分别与&String
进行匹配,绑定模式设置为ref
。当我们将
&a
与&String
进行匹配时会发生什么?好吧,记住&
是一个引用模式,当匹配引用模式时,我们完全忽略绑定模式。因此,我们将&a
与&String
进行匹配,绑定模式重置为move
。这将从两侧删除引用。剩下的是a = String
。&b
也是一样。此代码片段中的下一个示例是相同的。
hmae6n7t2#
这被称为"解构",通常用于模式匹配,如
if let Some(val) = option
。基本上,这是:
相当于:
它相当于:
为了解释更多,我将使用一个更简单的例子。
重构的工作方式是"简化"两端。在这种情况下,它将取消对两端的引用,从而导致
这也可以通过间接方式实现,如果我们在中间添加一个步骤:
它可以看穿间接寻址,现在将解引用
ref_to_value
以再次获得String
。匹配人机工程学也可能涉及,但仅用于解引用整个元组。
注意:要在重构赋值中获取引用,可以使用
ref
:通常这只用于模式匹配,如: