当变量从未重新分配时,为什么我需要使用 mut?

问题描述 投票:0回答:2

我以为我理解何时以及为何需要在 Rust 代码中使用

mut
,但我不明白为什么需要在以下代码中使用
mut

use rand::{thread_rng, Rng};

fn main() {
    let mut rng = thread_rng(); // Why does this need to be mutable?
    println!("{:?}", rng); // outputs ThreadRng { rng: UnsafeCell }

    let x: u32 = rng.gen();
    println!("{}", x);

    println!("{:?}", rng); // outputs the same result as ThreadRng { rng: UnsafeCell }
}

让我解释一下。变量

rng
被声明为可变的,但它的值在整个代码中永远不会改变,那么为什么我需要使
rng
变量可变呢?

rust
2个回答
5
投票

有两种情况需要将变量声明为可变:

  1. 当您通过赋值更改它的值时(您可能已经理解这一点):
let mut my_var = 5i32;
println!("old value {}", my_var);
my_var = my_var * 2;
println!("old value {}", my_var);
  1. 当你获得对它的可变引用时:
let mut my_var = 5i32;
println!("old value {}", my_var);
let reference = &mut my_var;
*reference = 10; // Mutable reference allow us change the value
println!("old value {}", my_var);

在上面的代码中会非常令人惊讶,因为如果我们将

my_var 
声明为
let my_var 
,因为它是实际值的变化。

您的情况正是如此。您调用此方法:https://docs.rs/rand/0.8.3/rand/trait.Rng.html#method.gen 您的代码可以改写为:

let x: u32 = Rng::gen(&mut rng);

如您所见,您通过调用使用

&mut self
的方法来获取可变引用。


4
投票

我怀疑这个问题是由于 Rust 中的“变量”概念与 Python 或 Java 等托管语言中的“变量”概念不同而引起的。在Python中,变量和它所保存的对象之间存在隐式间接,因此:

n = 42
n += 1         # the variable changed, but not the value (here immutable)

# however:
l = []
l.append(42)   # the value changed, but not the variable
l = [1, 2, 3]  # the variable changed, but not the value

在第二部分中,变量

l
指的是一个独立于该变量存在的列表对象。该对象包含列表元素和元信息(例如列表大小)。
l.append(42)
l[0] = 42
改变列表对象,而
l = [1, 2, 3]
改变变量以引用不同的(新创建的)列表对象。多个变量可以轻松引用同一个对象,这可能是一个有用的功能,也可能是错误的来源。

Rust 和 C++ 这样的语言不是这样工作的。变量与其包含的值之间没有隐式间接,变量存储实际值。 (间接仍然可以存在,但它们要么使用

Box
Rc
显式存在,要么隐藏在对象的实现中,如
Vec
的情况,但语言永远不会提供,它们始终必须是明确请求。)并且两个变量不能包含相同的对象,因为对象变量。例如:

let mut n = 42;
n += 1;             // the contents of the variable changed

// but also:
let mut l = vec![];
l.push(42);         // the contents of the variable changed
l = vec![1, 2, 3];  // the contents of the variable changed

换句话说,构成向量的数据(的三元组)存储在变量中,因此向量的每个突变都被视为对变量的修改。例如,当您向向量追加一个元素时,向量的长度将会改变,因此直接存储在 l

 中的数据实际上会被修改。在 
mut
 上调用 
l
 方法和分配给 
l
 之间的唯一区别是,在分配的情况下,所有字段都可能立即更改。但两者之间并没有像 Python 中那样的根本区别——两者都有修改变量中存储的内容的效果。由于变量不引用独立存在的对象,因此就语言而言,这两种类型的变异是无法区分的。

这同样适用于

rng

 变量:它保存一个 RNG 实例,当您生成下一个数字时,其状态必须更改。这种状态更改算作变量的更改,就像为其分配一个全新的 RNG 一样。 Rust 中的值没有固有的“身份”,无法将分配与其他类型的状态突变区分开来。

如果你想模拟Python语义,你可以使用智能指针:

// doesn't need to be mutable, holds reference to heap-allocated RNG let rng = Rc::new(RefCell::new(thread_rng())); // RefCell::borrow_mut() grants permission to mutate the shared object, // but `rng` (the Rc pointer) is still unchanged let x: u32 = rng.borrow_mut().gen(); // as expected, this wouldn't compile because rng is not mut: //rng = Rc::new(RefCell::new(thread_rng()));
    
© www.soinside.com 2019 - 2024. All rights reserved.