变量与常量

在 Rust 中,变量绑定默认是不可变的(immutable)。这是 Rust 安全性保证的基石之一。

变量绑定

使用 let 关键字创建变量绑定:

fn main() {
    let x = 5;
    println!("x 的值是: {}", x);
}

不可变性(Immutability)

默认情况下,变量是不可变的

fn main() {
    let x = 5;
    // x = 6;  // ❌ 编译错误:cannot assign twice to immutable variable
    println!("x = {}", x);
}

为什么默认不可变?

  • 🔒 安全性: 防止意外修改
  • 🚀 并发: 多线程环境下更安全
  • 🧠 可读性: 代码更容易理解
  • 优化: 编译器可以做更多优化

可变变量

使用 mut 关键字声明可变变量:

fn main() {
    let mut x = 5;
    println!("x 的初始值: {}", x);

    x = 6;  // ✅ 可以修改
    println!("x 的新值: {}", x);
}
Loading diagram...

变量遮蔽(Shadowing)

Rust 允许用相同名称声明新变量,"遮蔽"旧变量:

fn main() {
    let x = 5;

    let x = x + 1;  // 遮蔽:创建新变量

    {
        let x = x * 2;  // 内层作用域的遮蔽
        println!("内层 x = {}", x);  // 12
    }

    println!("外层 x = {}", x);  // 6
}

遮蔽 vs 可变性

遮蔽的优势

  1. 可以改变类型
fn main() {
    let spaces = "   ";           // &str 类型
    let spaces = spaces.len();    // ✅ usize 类型,遮蔽允许改变类型

    // 对比可变变量(不允许改变类型):
    let mut count = "   ";
    // count = count.len();       // ❌ 编译错误:类型不匹配
}
  1. 保持不可变性
fn main() {
    let x = 5;
    let x = x + 1;  // 新的 x 仍然是不可变的
    // x = 10;      // ❌ 不能修改
}
Loading diagram...

常量(Constants)

常量使用 const 关键字声明,并且必须标注类型:

const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159;

fn main() {
    println!("最大点数: {}", MAX_POINTS);
}

常量的特点

特性常量不可变变量
关键字constlet
类型标注必须可选(类型推导)
作用域可以是全局通常是局部
编译时确定运行时确定
表达式只能是常量表达式任意表达式
命名规范SCREAMING_SNAKE_CASEsnake_case

常量 vs 变量

// ✅ 常量:编译时求值
const SECONDS_IN_HOUR: u32 = 60 * 60;

fn main() {
    // ✅ 不可变变量:运行时求值
    let runtime_value = get_value();

    // ❌ 常量不能使用运行时函数
    // const WRONG: u32 = get_value();
}

fn get_value() -> u32 {
    42
}

类型推导

Rust 有强大的类型推导能力:

fn main() {
    let x = 5;           // 推导为 i32
    let y = 2.0;         // 推导为 f64
    let is_true = true;  // 推导为 bool

    // 显式类型标注
    let z: u64 = 100;

    // 通过使用方式推导
    let mut guess = String::new();
    guess.push_str("42");
    let guess: i32 = guess.trim().parse().expect("Not a number!");
}

数字分隔符

为了提高可读性,可以在数字中使用下划线:

fn main() {
    let million = 1_000_000;
    let hex = 0xff_ff_ff;
    let binary = 0b1111_0000;
    let float = 1_234.567_890;

    println!("一百万: {}", million);
}

实践建议

✅ 推荐做法

fn main() {
    // 1. 默认使用不可变变量
    let price = 100;

    // 2. 只在需要时使用 mut
    let mut count = 0;
    count += 1;

    // 3. 常量用于魔法数字
    const TAX_RATE: f64 = 0.08;
    let total = price as f64 * (1.0 + TAX_RATE);

    // 4. 使用遮蔽改变类型
    let input = "42";
    let input: i32 = input.parse().unwrap();
}

❌ 避免做法

fn main() {
    // ❌ 不必要的 mut
    let mut x = 5;  // 如果后续不修改,不要用 mut
    println!("{}", x);

    // ❌ 魔法数字(应该用常量)
    let tax = price * 0.08;  // 0.08 是什么?

    // ❌ 过度使用遮蔽(可读性差)
    let x = 1;
    let x = x + 1;
    let x = x * 2;
    let x = x - 1;
    // 太多遮蔽会让代码难以追踪
}

内存视角

变量绑定在内存中的表示:

fn main() {
    let x: i32 = 42;
    let y: i32 = x;  // Copy trait: 栈上复制

    println!("x = {}, y = {}", x, y);
}

栈上的布局

栈内存:
┌──────────┐
│  x: 42   │  ← i32 在栈上,4 字节
├──────────┤
│  y: 42   │  ← 复制值,不是引用
└──────────┘

练习题

练习 1:修复编译错误

fn main() {
    let x = 5;
    x = 6;  // ❌ 编译错误
    println!("{}", x);
}
查看答案
fn main() {
    let mut x = 5;  // 添加 mut 关键字
    x = 6;
    println!("{}", x);
}

练习 2:使用遮蔽

将字符串转换为数字,使用遮蔽技术:

fn main() {
    let guess = "42";
    // 在这里将 guess 转换为数字(提示:使用遮蔽)
}
查看答案
fn main() {
    let guess = "42";
    let guess: i32 = guess.parse().expect("Not a number!");
    println!("数字是: {}", guess);
}

小结

  • ✅ Rust 中的变量默认不可变
  • ✅ 使用 mut 关键字声明可变变量
  • 遮蔽允许重新绑定变量,可以改变类型
  • 常量const 声明,必须标注类型,编译时求值
  • ✅ 不可变性带来安全性并发性优化优势
  • ✅ 优先使用不可变,只在必要时使用可变

下一步,我们将学习 Rust 的数据类型系统