参考:https://kaisery.github.io/trpl-zh-cn/ch03-01-variables-and-mutability.htmlhttps://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html

不可变变量

含义

一旦值被绑定一个名称上,就不能改变这个值。Rust 中变量默认不可变。
例如: let x = 1;

优势

  1. 充分利用 Rust 提供的安全性和简单并发性来编写代码
  2. Rust 编译器不需要追踪一个值如何和在哪可能会被改变,从而使得代码易于推导
  3. 遮蔽机制 (shadowing):可以使用 let 创建新的变量(或者称为重新定义变量、重新绑定变量)来改变值,甚至类型。如果新的值是变量改变前的类型,使用 mut 有相同的作用,然而前后类型不同时,使用 mut 是做不到的 ,例如

获取了字符串,然而关注的是其长度 (字符串可以扔掉),可以这样使用

  1. let spaces = " ";
  2. let spaces = spaces.len(); // shadow 上一个 spaces ,并且类型从字符串变成了整型

而同样的场景使用 mut
直接改变 spaces 的值不可行:

  1. let mut spaces = " ";
  2. spaces = spaces.len(); // error: expected &str, found usize

重新绑定,虽然程序通过,但得到 not need to be mutable 警告:

  1. let mut spaces = " "; // warning: variable does not need to be mutable
  2. let mut spaces = spaces.len(); // warning: variable does not need to be mutable
  1. let mut spaces = " ";
  2. let spaces = spaces.len(); // warning: variable does not need to be mutable

shadowing 和 scope 同时使用的例子:

  1. fn main() {
  2. // 此绑定生存于 main 函数中
  3. let long_lived_binding = 1;
  4. // 这是一个代码块,比 main 函数拥有更小的作用域
  5. {
  6. // 此绑定只存在于本代码块
  7. let short_lived_binding = 2;
  8. println!("inner short: {}", short_lived_binding);
  9. println!("inner long: {}", long_lived_binding);
  10. // 此绑定*掩蔽*了外面的绑定
  11. let long_lived_binding = 5_f32;
  12. println!("inner long after shadowed: {}", long_lived_binding);
  13. }
  14. // 代码块结束
  15. // 报错!`short_lived_binding` 在此作用域上不存在
  16. // println!("outer short: {}", short_lived_binding);
  17. println!("outer long: {}", long_lived_binding);
  18. // 此绑定同样*掩蔽*了前面的绑定
  19. let long_lived_binding = 'a';
  20. println!("outer long after shadowed: {}", long_lived_binding);
  21. }

打印结果:

  1. inner short: 2
  2. inner long: 1
  3. inner long after shadowed: 5
  4. outer long: 1
  5. outer long after shadowed: a

不可变与常量的区别

  1. 声明方式:不可变使用 let ,通常编译器会提示使用 snakecase(使用 `连接单词,且每个单词小写);常量使用const且必须标注类型,通常编译器会提示使用 UPPER_SNAKE_CASE (使用_` 连接单词,且每个单词大写)
  2. 表达式类型:不可变变量可接受多种表达式;常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值
  3. 定义/绑定名称次数:不可变变量允许多次把名称绑定新的值,并且这种绑定可以是可变也可以是不可变(但会出现警告),但不能是常量绑定(即用 const 绑定);常量不允许把名称再次绑定值(无论是否为常量绑定),也就是只能定义一次名称。例如下面的例子:

不可变变量允许多次绑定:

  1. fn main() {
  2. let x = 1;
  3. let x = 2; // x 可以再次进行绑定不可变值的操作,或者说此处的 x 隐藏了(shadow)上一行的 x
  4. let mut x = 3; // x 可以再次进行绑定可变值的操作,但是出现 not need to be mutable 警告
  5. println!("{}", x);
  6. }

image.png
常量只允许一次绑定:

  1. fn main() {
  2. const Y: u32 = 2;
  3. let Y: u32 = 1; // Y 不可以再次进行 let 绑定值的操作,即便标注相同的类型。常量不能当做变量使用。
  4. println!("{}", Y);
  5. }

image.png

  1. fn main() {
  2. const Y: u32 = 2;
  3. const Y: u32 = 1; // Y 不可以再次进行 const 绑定值的操作,即便标注相同的类型。常量只能定义一次(must be defined only once)。
  4. println!("{}", Y);
  5. }

image.png
不可变量、常量、静态常/变量的区别:见 “unsafe Rust”
20.1 unsafe Rust

可变变量

含义

允许改变值、表明其他代码将会改变这个变量值的意图。在变量名之前加 mut 来使其可变。
例如 let mut x = 1;

优势

  1. 与只用不可变变量相比,可变性会让代码更容易编写
  2. 使用大型数据结构时,适当地使用可变变量,可能比复制和返回新分配的实例更快
  3. (可读性)对于较小的数据结构,总是创建新实例,采用更偏向函数式的编程风格,可能会使代码更易理解

    劣势

  4. 牺牲性能

  5. 更容易出现 bug (与编译器作斗争)

    变量先声明

    可以先声明(declare)变量绑定,后面才将它们初始化(initialize)。但是这种做法很 少用,因为这样可能导致使用未初始化的变量。
    编译器禁止使用未经初始化的变量,因为这会产生未定义行为(undefined behavior)。
    1. fn main() {
    2. // 声明一个变量绑定
    3. let a_binding;
    4. {
    5. let x = 2;
    6. // 初始化一个绑定
    7. a_binding = x * x;
    8. }
    9. println!("a binding: {}", a_binding);
    10. let another_binding;
    11. // 报错!使用了未初始化的绑定
    12. println!("another binding: {}", another_binding);
    13. // 改正 ^ 注释掉此行
    14. another_binding = 1;
    15. println!("another binding: {}", another_binding);
    16. }