Rust 中不可变变量和常量变量的区别是什么?

我了解到,如果一个变量没有使用 mut显式地声明为可变的,那么它就变成了不可变的(声明之后不能更改它)。那为什么我们在 Rust 里有 const关键字?他们不是一样的吗?如果没有,它们有什么不同?

19338 次浏览

const is for compile-time constants with everything that entails. For example, you can create a fixed-sized array whose size is a const, but you can't do that with a let binding. Of course, this also means that you can put far, far more things into a let binding than into a const.

const is not for variables; it's for constant values which may not be stored anywhere; they're effectively an alias for a literal value.

Non-mut let declares an actual variable which is created at runtime, can be moved (and no longer accessible), and even have interior mutability (if it contains Cell members, for example) in some cases.

Constants can not be shadowed:

let x = 10u32;
const Y:u32 = 20u32;


let x = 11u32;
//error: duplicate definition of value `Y` [E0428]
//const Y:u32 = 21u32;


println!("x={} Y={}",x,Y); //x=11 Y=20

const, in Rust, is short for constant and is related to compile-time evaluation. It shows up:

  • when declaring constants: const FOO: usize = 3;
  • when declaring compile-time evaluable functions: const fn foo() -> &'static str

These kinds of values can be used as generic parameters: [u8; FOO]. For now this is limited to array size, but there is talk, plans, and hope to extend it further in the future.

By contrast, a let binding is about a run-time computed value.

Note that despite mut being used because the concept of mutability is well-known, Rust actually lies here. &T and &mut T are about aliasing, not mutability:

  • &T: shared reference
  • &mut T: unique reference

Most notably, some types feature interior mutability and can be mutated via &T (shared references): Cell, RefCell, Mutex, etc.


Note: there is an alternative use of mut and const with raw pointers (*mut T and *const T) which is not discussed here.

Additionally, we can't make global items using let, but it's possible by using const. Here is an example.

const LENGTH:usize = 4;


fn main() {
let arr:[i32; LENGTH] = [10,20,30,40];


for i in 0..LENGTH{
println!("{}", arr[i])
}
}

for more information about the usages of const, static, and let: const and static

The story is a little bit longer.

A const does not represent a memory location but a value. const values are directly inlined at usage location. Any temporary objects that are created during expression evaluation are accessible only by the compiler during compile time. Can be scoped globally. Can not reference runtime items. Must be type annotated.

Let values represent a memory location. Immutability for let binding is a compiler enforced thing can be changed with mut modifier. It is runtime construct. Always locally scopped. Their types can be inferred by the compiler.

For completeness, a static also represents a memory location like let but any references to the same static are actually a reference to the same memory location. Static are, well, statics. They are compiled into executable and accessible for the entire lifetime of the running program. Can be scoped globally. Can reference other statics. Must be type annotated.

Conceptually, const items are substituted before parsing, akin to what a C-macro does. This enables it to be used in circumstances where normal variables are not allowed.

const TEN: u32 = 10;
let ten = 10;


// OK
match x {
TEN => do_a(),
_ => do_b(),
}


// Nah...
match x {
ten => do_a(),
_ => do_b(),
}


// ...use this instead
match x {
y if x == ten => do_a(),,
_ => do_b(),
}