我以为我理解何时以及为何需要在 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
变量可变呢?
有两种情况需要将变量声明为可变:
let mut my_var = 5i32;
println!("old value {}", my_var);
my_var = my_var * 2;
println!("old value {}", my_var);
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
的方法来获取可变引用。
我怀疑这个问题是由于 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()));