在 Rust 中,"shadowing" 和 "mutability" 有什么区别?

In Rust, what's the difference between "shadowing" and "mutability"?

Chapter 3 of the Rust Book变量和可变性中,我们对这个主题进行了几次迭代,以演示 Rust 中变量的默认、不可变行为:

fn main() {
    let x = 5;
    println!("The value of x is {}", x);
    x = 6;
    println!("The value of x is {}", x);
}

输出:

error[E0384]: cannot assign twice to immutable variable `x`
 --> src/main.rs:4:5
  |
2 |     let x = 5;
  |         -
  |         |
  |         first assignment to `x`
  |         help: make this binding mutable: `mut x`
3 |     println!("The value of x is {}", x);
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable

但是,由于 Rust 对阴影 变量的处理,我们可以简单地这样做来更改尽管如此 "immutable" x:

fn main() {
    let x = 5;
    println!("The value of x is {}", x);
    let x = 6;
    println!("The value of x is {}", x);
}

哪些输出(跳过细节):

The value of x is 5
The value of x is 6

有趣的是,这段代码还产生了以上两行作为输出,尽管事实上我们没有调用 let 而是 mut 第一次 x 是绑定到 5:

fn main() {
    let mut x = 5;
    println!("The value of x is {}", x);
    x = 6;
    println!("The value of x is {}", x);
}

这种在如何(并非真正)保护变量免于重新分配方面的歧义似乎与保护绑定到不可变(Rust 默认情况下)变量的值的既定目标背道而驰。来自同一章(其中还包含 Shadowing 部分):

It’s important that we get compile-time errors when we attempt to change a value that we previously designated as immutable because this very situation can lead to bugs. If one part of our code operates on the assumption that a value will never change and another part of our code changes that value, it’s possible that the first part of the code won’t do what it was designed to do. The cause of this kind of bug can be difficult to track down after the fact, especially when the second piece of code changes the value only sometimes.

In Rust, the compiler guarantees that when you state that a value won’t change, it really won’t change. That means that when you’re reading and writing code, you don’t have to keep track of how and where a value might change. Your code is thus easier to reason through.

如果我可以通过对 let 的足够无辜的调用来回避我的不可变 x 的这一重要特性,为什么我需要 mut?有没有什么方法可以让 x 变得不可变,这样就没有 let x 可以重新分配它的值了?

我认为混淆是因为您将名称与存储混为一谈。

fn main() {
    let x = 5; // x_0
    println!("The value of x is {}", x);
    let x = 6; // x_1
    println!("The value of x is {}", x);
}

在此示例中,有一个名称(x)和两个存储位置(x_0x_1)。第二个 let 只是重新绑定名称 x 以引用存储位置 x_1x_0 存储位置完全不受影响。

fn main() {
    let mut x = 5; // x_0
    println!("The value of x is {}", x);
    x = 6;
    println!("The value of x is {}", x);
}

在这个例子中,有一个名称(x)和一个存储位置(x_0)。 x = 6赋值是直接改变存储位置x_0.

的位

您可能会争辩说它们做同样的事情。如果是这样,你就错了:

fn main() {
    let x = 5; // x_0
    let y = &x; // y_0
    println!("The value of y is {}", y);
    let x = 6; // x_1
    println!("The value of y is {}", y);
}

这输出:

The value of y is 5
The value of y is 5

这是因为更改 x 指向的存储位置对 x_0 的存储位置绝对没有影响,y_0 包含指向的位置。然而,

fn main() {
    let mut x = 5; // x_0
    let y = &x; // y_0
    println!("The value of y is {}", y);
    x = 6;
    println!("The value of y is {}", y);
}

编译失败,因为你不能在借用时改变 x_0

Rust 关心防止不需要的突变影响正如通过参考文献观察到的那样。这与允许遮蔽并不冲突,因为您在遮蔽时并没有改变值,您只是以一种在其他任何地方都无法观察到的方式改变特定名称的含义。阴影是一种严格的局部变化。

所以是的,您绝对可以防止 x 的值被更改。您不能 做的是保持名称x 所指的内容不被更改。最多,您可以使用 clippy 之类的东西来拒绝阴影作为 lint。