模式匹配引用时的奇怪类型
Weird type when pattern matching references
我在读这个 的时候遇到了这个奇怪的行为,这个 post 的核心问题是 当你匹配 (&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
}
我添加到行尾的a
和b
的类型注释是由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
.
的类型
我的问题是:
- 为什么
a
和 b
总是具有相同的类型,即使在代码片段 1/2/3 中 RHS 具有不同的类型(x/ref_to_x/ref_ref_to_x..)?
- 这种匹配是如何发生的(如果有一个逐步的匹配过程,我们将不胜感激)?
- 如何在写代码时得到与
rust-analyzer/rustc
完全相同的类型推断?
顺便说一句,这与 rfc 2005 match-ergonomics 相关吗?我在谷歌上搜索了很多,发现很多人在他们的回答中提到了这一点。
这称为“解构”。它常用于模式匹配,如 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 { ... }
是的。您所看到的是符合人体工程学的实际应用,它们的行为可能并非您所期望的那样。
匹配人体工程学的工作方式是使用绑定模式。提供三种绑定模式,即使不匹配人体工程学也可以使用:
- 搬家。这是引入匹配人体工程学之前的默认绑定模式,它总是尝试移动(或复制)值。
ref
。这是将 ref
运算符应用于绑定(令人惊讶)时得到的结果,它添加了 one 引用。例如,在match e { ref r => ... }
中,r
是&e
。
ref mut
。类似于 ref
,但使用可变借用(并使用 ref mut
运算符指定)。
该过程的工作原理如下:编译器从外向内处理模式。该过程以 move
作为绑定模式开始。
每次编译器需要将 non-reference 模式(文字、结构、元组、切片)与引用匹配时,它会自动取消引用引用并更新绑定模式:当 &
引用匹配时,我们将获得 ref
绑定模式,对于 &mut
引用,如果当前绑定模式为 [=],我们将获得 ref
14=] 或其他 ref mut
。然后重复此过程,直到我们不再有参考为止。
如果我们匹配引用模式(绑定、通配符、const
引用类型或&
/&mut
patterns),默认绑定模式重置回 move
.
绑定变量时,编译器会查看当前绑定模式:对于 move
,它将匹配类型 as-is。对于 ref
和 ref mut
,它将分别添加 &
或 &mut
。 但只有一个.
让我们效仿你的例子line-by-line。
let (a, b) = x; // type of a: &&String, type of b: &&String
我们将 non-reference 模式(元组模式)与引用(&(&String, &String)
类型)进行匹配。所以我们取消引用引用并将绑定模式设置为 ref
.
现在我们得到了一个元组模式来匹配 (&String, &String)
类型的元组和 ref
的绑定模式。我们将 a
与 &String
进行匹配:这是一个参考模式(绑定),因此我们不会更改绑定模式。但是,我们已经有了ref
的绑定模式。我们匹配的类型是&String
,ref
表示我们添加了一个引用,所以我们以&&String
结束。 b
.
也发生了完全相同的事情
let (a, b) = ref_to_x; // type of a: &&String, type of b: &&String
在这里,就像前面的示例一样,我们将 non-reference 模式(元组模式)与引用 (&&(&String, &String)
) 进行匹配。所以我们取消引用并将绑定模式设置为 ref
。但是我们还有一个引用:&(&String, &String)
。所以我们再次取消引用。绑定模式已经是ref
,我们就不用碰了。我们以 (a, b)
与 (&String, &String)
匹配结束。这意味着 a = &String
、b = &String
。但请记住我们使用的是 ref
绑定模式,所以我们应该添加一个引用。我们只添加了一个参考,即使我们匹配了两个!最后,我们有a = &&String
,b = &&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
这是最有趣的一个。还记得我们是如何谈论参考与 non-reference 模式的吗?在这个例子中,事实起着至关重要的作用。
首先我们将元组模式与类型 &(&String, &String)
进行匹配。我们取消引用元组并设置 binding_mode = ref
。现在我们匹配元组:我们必须将 &a
和 &b
分别与 &String
匹配,绑定模式设置为 ref
.
当我们将 &a
与 &String
匹配时会发生什么?好吧,记住 &
是一个参考模式,在匹配参考模式时我们 完全忽略绑定模式 。所以我们将 &a
与 &String
进行匹配,绑定模式重置为 move
。这从两边删除了引用,给我们留下 a = String
。 &b
.
相同
此代码段中的下一个示例是相同的。
我在读这个 (&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
}
我添加到行尾的a
和b
的类型注释是由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
.
我的问题是:
- 为什么
a
和b
总是具有相同的类型,即使在代码片段 1/2/3 中 RHS 具有不同的类型(x/ref_to_x/ref_ref_to_x..)? - 这种匹配是如何发生的(如果有一个逐步的匹配过程,我们将不胜感激)?
- 如何在写代码时得到与
rust-analyzer/rustc
完全相同的类型推断?
顺便说一句,这与 rfc 2005 match-ergonomics 相关吗?我在谷歌上搜索了很多,发现很多人在他们的回答中提到了这一点。
这称为“解构”。它常用于模式匹配,如 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 { ... }
是的。您所看到的是符合人体工程学的实际应用,它们的行为可能并非您所期望的那样。
匹配人体工程学的工作方式是使用绑定模式。提供三种绑定模式,即使不匹配人体工程学也可以使用:
- 搬家。这是引入匹配人体工程学之前的默认绑定模式,它总是尝试移动(或复制)值。
ref
。这是将ref
运算符应用于绑定(令人惊讶)时得到的结果,它添加了 one 引用。例如,在match e { ref r => ... }
中,r
是&e
。ref mut
。类似于ref
,但使用可变借用(并使用ref mut
运算符指定)。
该过程的工作原理如下:编译器从外向内处理模式。该过程以 move
作为绑定模式开始。
每次编译器需要将 non-reference 模式(文字、结构、元组、切片)与引用匹配时,它会自动取消引用引用并更新绑定模式:当 &
引用匹配时,我们将获得 ref
绑定模式,对于 &mut
引用,如果当前绑定模式为 [=],我们将获得 ref
14=] 或其他 ref mut
。然后重复此过程,直到我们不再有参考为止。
如果我们匹配引用模式(绑定、通配符、const
引用类型或&
/&mut
patterns),默认绑定模式重置回 move
.
绑定变量时,编译器会查看当前绑定模式:对于 move
,它将匹配类型 as-is。对于 ref
和 ref mut
,它将分别添加 &
或 &mut
。 但只有一个.
让我们效仿你的例子line-by-line。
let (a, b) = x; // type of a: &&String, type of b: &&String
我们将 non-reference 模式(元组模式)与引用(&(&String, &String)
类型)进行匹配。所以我们取消引用引用并将绑定模式设置为 ref
.
现在我们得到了一个元组模式来匹配 (&String, &String)
类型的元组和 ref
的绑定模式。我们将 a
与 &String
进行匹配:这是一个参考模式(绑定),因此我们不会更改绑定模式。但是,我们已经有了ref
的绑定模式。我们匹配的类型是&String
,ref
表示我们添加了一个引用,所以我们以&&String
结束。 b
.
let (a, b) = ref_to_x; // type of a: &&String, type of b: &&String
在这里,就像前面的示例一样,我们将 non-reference 模式(元组模式)与引用 (&&(&String, &String)
) 进行匹配。所以我们取消引用并将绑定模式设置为 ref
。但是我们还有一个引用:&(&String, &String)
。所以我们再次取消引用。绑定模式已经是ref
,我们就不用碰了。我们以 (a, b)
与 (&String, &String)
匹配结束。这意味着 a = &String
、b = &String
。但请记住我们使用的是 ref
绑定模式,所以我们应该添加一个引用。我们只添加了一个参考,即使我们匹配了两个!最后,我们有a = &&String
,b = &&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
这是最有趣的一个。还记得我们是如何谈论参考与 non-reference 模式的吗?在这个例子中,事实起着至关重要的作用。
首先我们将元组模式与类型 &(&String, &String)
进行匹配。我们取消引用元组并设置 binding_mode = ref
。现在我们匹配元组:我们必须将 &a
和 &b
分别与 &String
匹配,绑定模式设置为 ref
.
当我们将 &a
与 &String
匹配时会发生什么?好吧,记住 &
是一个参考模式,在匹配参考模式时我们 完全忽略绑定模式 。所以我们将 &a
与 &String
进行匹配,绑定模式重置为 move
。这从两边删除了引用,给我们留下 a = String
。 &b
.
此代码段中的下一个示例是相同的。