在Rust中,“shadowing”和“mutability”的区别是什么?

35
在Rust书籍的第三章节变量和可变性中,我们通过几次迭代来展示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对变量的shadowing处理方式,我们可以通过以下方法改变仍然“不可变”的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

有趣的是,尽管我们第一次将x绑定到5时没有调用let而是调用了mut,但此代码也会产生上述一对行作为输出:
fn main() {
    let mut x = 5;
    println!("The value of x is {}", x);
    x = 6;
    println!("The value of x is {}", x);
}

变量在不被重新分配时(实际上并未)如何保护似乎与 Rust 默认保护不可变变量绑定的值的目标相矛盾。来自同一章节(还包含 Shadowing 部分):

当我们尝试更改先前指定为不可变的值时,重要的是我们获得编译时错误,因为这种情况可能会导致错误。如果我们代码的一部分操作假设一个值永远不会改变,而我们代码的另一部分更改了该值,那么第一部分代码可能无法执行其设计目的。这种错误的原因可能在事后难以跟踪,特别是当第二段代码只有在某些时候更改值时。

在 Rust 中,编译器保证当您声明一个值不会更改时,它确实不会更改。这意味着在读写代码时,您不必跟踪值可能更改的方式和位置。因此,您的代码更容易理解。

如果我可以通过足够无辜的调用let来使我的不可变x的这个重要特性被规避,那么我为什么需要mut?是否有一种真正且严肃的方法可以使x不可变,以便没有let x可以重新分配其值?

1个回答

63

我认为这其中的混淆是因为您将名称与存储混淆了。

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_1。存储位置x_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的遮蔽。


非常好的答案,特别是关于由于被借用而导致失败的部分。我会等几天然后接受。是否有一种方法可以打印链接到同一变量的所有值(例如,在此示例中,如果我想查看所有x_i的值,对于x来说,这种情况下为x_0和x_1)? - d8aninja
@d8aninja 不是。 - Shepmaster
5
“不可变变量”这个表述没有让人感到意外吗?它们不是反义词吗? - bN_

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接