匹配人体工程学保留

摘要

  • 只有当模式直到绑定处都是完全显式的(即,当它不使用匹配人体工程学时),才允许在绑定上编写 mutrefref mut
    • 换句话说,当默认绑定模式不是 move 时,在绑定上编写 mutrefref mut 是错误的。
  • 引用模式 (&&mut) 仅允许在模式的完全显式前缀中使用。
    • 换句话说,只有当默认绑定模式为 move 时,引用模式才能匹配被检查值的引用。

详情

背景

matchlet 和其他结构中,我们将模式被检查值进行匹配。例如:

#![allow(unused)]
fn main() {
let &[&mut [ref x]] = &[&mut [()]]; // x: &()
//  ~~~~~~~~~~~~~~~   ~~~~~~~~~~~~
//      Pattern        Scrutinee
}

这样的模式被称为完全显式模式,因为它不会省略(即“跳过”或“忽略”)被检查值中的任何引用。相比之下,这个其他方面等效的模式不是完全显式的

#![allow(unused)]
fn main() {
let [[x]] = &[&mut [()]]; // x: &()
}

像这样的模式被称为使用匹配人体工程学,最初在 RFC 2005 中引入。

在匹配人体工程学下,当我们逐步将模式与被检查值进行匹配时,我们会跟踪默认绑定模式。此模式可以是 moveref mutref 之一,并且它从 move 开始。当我们到达绑定时,除非提供了显式绑定模式,否则将使用默认绑定模式来决定绑定的类型。

例如,在这里我们提供了一个显式绑定模式,导致 x 通过引用绑定

#![allow(unused)]
fn main() {
let ref x = (); // &()
}

相比之下

#![allow(unused)]
fn main() {
let [x] = &[()]; // &()
}

在这里,在模式中,我们传递了被检查值中的外部共享引用。这导致默认绑定模式从 move 切换到 ref。由于没有指定显式绑定模式,因此在绑定 x 时使用 ref 绑定模式。

mut 限制

在 Rust 2021 及更早版本中,我们允许这种奇怪的情况

#![allow(unused)]
fn main() {
let [x, mut y] = &[(), ()]; // x: &(), mut y: ()
}

在这里,因为我们在模式中传递了共享引用,所以默认绑定模式切换到 ref。但是,在这些版本中,在绑定上写入 mut 会将默认绑定模式重置为 move

这可能会令人惊讶,因为可变性应该影响类型并不直观。

为了留出空间来修复这个问题,在 Rust 2024 中,当默认绑定模式不是 move 时,在绑定上编写 mut 是错误的。也就是说,只有当模式(直到该绑定处)是完全显式的时,才能在绑定上编写 mut

在 Rust 2024 中,我们可以将上面的例子写成

#![allow(unused)]
fn main() {
let &[ref x, mut y] = &[(), ()]; // x: &(), mut y: ()
}

ref / ref mut 限制

在 Rust 2021 及更早版本中,我们允许

#![allow(unused)]
fn main() {
let [ref x] = &[()]; // x: &()
}

在这里,ref 显式绑定模式是冗余的,因为通过传递共享引用(即在模式中不提及它),绑定模式切换到 ref

为了为其他语言可能性留出空间,我们不允许在 Rust 2024 中使用冗余的显式绑定模式。我们可以将上面的例子简单地重写为

#![allow(unused)]
fn main() {
let [x] = &[()]; // x: &()
}

引用模式限制

在 Rust 2021 及更早版本中,我们允许这种奇怪的情况

#![allow(unused)]
fn main() {
let [&x, y] = &[&(), &()]; // x: (), y: &&()
}

在这里,模式中的 & 既匹配 &() 上的引用,又将默认绑定模式重置为 move。这可能会令人惊讶,因为模式中的单个 & 会导致类型发生比预期更大的变化,从而删除了两层引用。

为了留出空间来修复这个问题,在 Rust 2024 中,当默认绑定模式不是 move 时,在模式中编写 &&mut 是错误的。也就是说,只有当模式(直到该点)是完全显式的时,才能编写 &&mut

在 Rust 2024 中,我们可以将上面的例子写成

#![allow(unused)]
fn main() {
let &[&x, ref y] = &[&(), &()];
}

迁移

rust_2024_incompatible_pat lint 标记了 Rust 2024 中不允许的模式。此 lint 是 rust-2024-compatibility lint 组的一部分,该组在运行 cargo fix --edition 时会自动应用。此 lint 将自动将受影响的模式转换为完全显式模式,这些模式在 Rust 2024 和所有早期版本中均可正确工作。

要迁移您的代码以与 Rust 2024 兼容,请运行

cargo fix --edition

例如,这将转换这个...

#![allow(unused)]
fn main() {
let [x, mut y] = &[(), ()];
let [ref x] = &[()];
let [&x, y] = &[&(), &()];
}

...变成这个

#![allow(unused)]
fn main() {
let &[ref x, mut y] = &[(), ()];
let &[ref x] = &[()];
let &[&x, ref y] = &[&(), &()];
}

或者,您可以手动启用 lint 以查找需要迁移的模式

#![allow(unused)]
fn main() {
// Add this to the root of your crate to do a manual migration.
#![warn(rust_2024_incompatible_pat)]
}