案例4: 变量与常量的定义及使用(let/const关键字实践)

本文是《Rust开发入门实战案例100例》中的第4个案例,深入讲解Rust中变量与常量的定义方式、作用域规则、可变性控制以及实际编程中的最佳实践。通过本案例的学习,读者将掌握 letconst 关键字的核心用法,理解 Rust 在内存安全和编译期检查方面的设计理念,并能够熟练运用这些基础知识构建可靠的程序结构。


一、变量与常量:Rust 编程的基石

在任何编程语言中,变量常量都是存储数据的基本手段。Rust 作为一门注重安全性与性能的系统级语言,在变量和常量的设计上体现了其独特的理念:默认不可变、显式声明可变、编译时严格检查。

与其他语言(如 Python 或 JavaScript)不同,Rust 中的变量默认是不可变的(immutable)。这意味着一旦一个值被绑定到变量名上,就不能再修改它——除非你明确地声明它是可变的。

这种设计有助于防止意外的数据更改,提升程序的安全性和可维护性。

1. 使用 let 定义变量

在 Rust 中,使用 let 关键字来声明变量:

let x = 5;

这行代码将值 5 绑定到变量 x 上。此时 x不可变的,如果你尝试修改它:

x = 6; // ❌ 编译错误!

编译器会报错:

error[E0384]: cannot assign twice to immutable variable `x`

要让变量可变,必须使用 mut 关键字:

let mut y = 5;
y = 6; // ✅ 合法,因为 y 被声明为可变
println!("y 的值是 {}", y); // 输出:y 的值是 6

2. 使用 const 定义常量

常量使用 const 关键字声明,且必须指定类型:

const MAX_POINTS: u32 = 100_000;

注意:

  • 常量名通常采用全大写加下划线格式(SCREAMING_SNAKE_CASE)
  • 类型标注是必需的(不能省略)
  • 只能用于表达式在编译时就能计算出结果的情况
  • 常量可以在任意作用域内声明,包括全局作用域

常量在整个程序生命周期中都有效,且不允许重新赋值。


二、代码演示:从基础到进阶实践

下面我们通过一系列递进式的代码示例,展示 letconst 的各种用法。

示例 1:基本变量声明与可变性控制

fn main() {
    let a = 10;
    let mut b = 20;

    println!("a = {}, b = {}", a, b);

    // a = 15; // ❌ 错误:无法修改不可变变量
    b = 25;     // ✅ 正确:b 是可变的

    println!("修改后:a = {}, b = {}", a, b);
}

输出:

a = 10, b = 20
修改后:a = 10, b = 25

⚠️ 提示:即使你不打算改变变量,也不要滥用 mut。保持变量不可变是一种良好的编程习惯,有助于减少副作用和逻辑错误。


示例 2:变量遮蔽(Shadowing)

Rust 支持“变量遮蔽”机制——即使用相同的名称重新声明变量,新的变量会“遮蔽”旧的变量:

fn main() {
    let x = "hello";
    let x = x.len(); // 遮蔽前一个 x,类型从 &str 变为 usize
    let x = x * 2;   // 再次遮蔽,x 变为整数并乘以 2

    println!("x = {}", x); // 输出:x = 10
}

优点

  • 允许在不引入新变量名的情况下转换数据类型或处理中间结果
  • 比可变变量更安全,因为每次遮蔽都会创建一个新的绑定,原值不会被修改

注意:只能用 let 进行遮蔽,不能对 const 或静态变量进行遮蔽。


示例 3:常量的跨作用域使用

// 全局常量
const PI: f64 = 3.14159265359;
const APP_NAME: &str = "MyRustApp";

fn main() {
    println!("应用名称:{}", APP_NAME);
    println!("圆周率近似值:{}", PI);

    let radius = 5.0;
    let area = PI * radius * radius;
    println!("半径为 {} 的圆面积是 {:.2}", radius, area);
}

输出:

应用名称:MyRustApp
圆周率近似值:3.14159265359
半径为 5 的圆面积是 78.54

📌 建议:将程序中频繁使用的固定值(如配置上限、协议版本号、数学常数等)定义为 const,提高可读性和维护性。


示例 4:结合模式解构进行变量声明

Rust 的 let 不仅能绑定单一值,还能配合模式匹配解构复合类型:

fn main() {
    // 解构元组
    let (name, age) = ("Alice", 25);
    println!("{name} 年龄是 {age}");

    // 解构数组的部分元素
    let numbers = [1, 2, 3, 4, 5];
    let [first, second, ..] = numbers;
    println!("前两个数字:{}, {}", first, second);

    // 忽略某些字段
    struct Point {
        x: i32,
        y: i32,
    }
    let point = Point { x: 10, y: 20 };
    let Point { x, .. } = point;
    println!("x 坐标是 {}", x);
}

输出:

Alice 年龄是 25
前两个数字:1, 2
x 坐标是 10

这是 Rust 强大模式匹配能力的体现,也是函数参数、循环遍历中常见的写法。


三、数据表格:变量与常量特性对比

特性 let 变量 const 常量
声明关键字 let / let mut const
是否可变 默认不可变,可用 mut 改变 ❌ 永远不可变
类型标注 可选(编译器自动推导) ✅ 必须显式标注
作用域 局部作用域或块作用域 任意作用域(含全局)
初始化时机 运行时或编译时 ✅ 必须在编译时确定
能否遮蔽 ✅ 可以多次 let 同名变量 ❌ 不支持遮蔽
内存位置 栈或堆(取决于类型) 编译时常量嵌入二进制
典型用途 存储临时数据、状态变化 数学常数、配置阈值、协议标识

💡 小贴士:const 更适合用于“永远不会变”的值;而 let mut 应谨慎使用,优先考虑不可变性 + 遮蔽的方式重构逻辑。


四、关键字高亮说明

在上述代码中,以下关键字具有特殊语义,需重点关注其语法行为:

  • let变量绑定关键字,用于将值绑定到标识符。
  • mut可变性修饰符,紧跟在 let 后表示该变量允许后续修改。
  • const常量声明关键字,用于定义编译期常量。
  • :类型标注操作符,用于指定变量或常量的类型。
  • =绑定/赋值操作符,左侧为变量名,右侧为表达式。

例如:

let mut count: u32 = 0;
// ↑    ↑     ↑     ↑
// │    │     │     └─ 初始值(运行时表达式)
// │    │     └─────── 类型标注(u32 表示无符号32位整数)
// │    └───────────── 可变性声明
// └────────────────── 变量绑定关键字

这些关键字共同构成了 Rust 中数据声明的基础语法体系。


五、分阶段学习路径

为了帮助初学者系统掌握变量与常量的使用,我们推荐以下五个阶段的学习路径:

🔹 阶段一:理解不可变性(第1周)

目标:建立“默认不可变”的思维习惯。

✅ 实践任务:

  • 编写多个只使用 let(无 mut)的程序
  • 故意尝试修改不可变变量,观察编译错误信息
  • 使用遮蔽代替可变变量完成简单计算链

📌 推荐练习:

let seconds_per_minute = 60;
let minutes_per_hour = 60;
let hours_per_day = 24;
let seconds_per_day = seconds_per_minute * minutes_per_hour * hours_per_day;
println!("{}", seconds_per_day);

🔹 阶段二:掌握 mut 与遮蔽的区别(第2周)

目标:理解何时使用 mut,何时使用遮蔽更合适。

✅ 实践任务:

  • 对比两种方式实现字符串长度处理:
    • 方式A:let mut s = String::new(); s.push_str("hello");
    • 方式B:let s = "hello"; let s = s.to_uppercase();
  • 分析各自的优缺点(安全性 vs 灵活性)

📌 提示:优先选择遮蔽,尤其是在类型转换或数据清洗场景中。


🔹 阶段三:学会使用 const 提升代码质量(第3周)

目标:识别项目中可以提取为常量的值。

✅ 实践任务:

  • 创建一个小型工具程序(如单位换算器),将所有固定系数定义为 const
  • 将日志级别、API 地址、超时时间等配置项设为常量

📌 示例:

const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;

🔹 阶段四:深入理解作用域与生命周期初步概念(第4周)

目标:了解变量的作用域规则,为后续学习打基础。

✅ 实践任务:

  • {} 块中声明变量,观察超出作用域后的不可访问性
  • 尝试在 if/else 分支中声明同名变量,体验遮蔽效果

📌 示例:

let x = 10;
{
    let x = "inner";
    println!("{}", x); // inner
}
println!("{}", x); // 10(外部 x 未受影响)

🔹 阶段五:综合应用与代码审查(第5周)

目标:写出符合 Rust 风格的高质量代码。

✅ 实践任务:

  • 审查已有代码,替换不必要的 mut 为遮蔽
  • 使用 clippy 工具检查变量命名、冗余可变性等问题
  • 编写单元测试验证常量正确性

📌 推荐命令:

cargo clippy -- -D clippy::pedantic

六、章节总结

本案例围绕 案例4:变量与常量的定义及使用 展开,全面介绍了 Rust 中 letconst 的核心语法与工程实践。以下是关键知识点回顾:

✅ 主要收获

  1. 变量默认不可变
    Rust 的设计哲学强调安全性,因此 let x = 5; 创建的是不可变绑定。若需修改,必须显式使用 let mut x

  2. const 用于编译期常量
    所有 const 必须带有类型标注,且值必须在编译时可知。适用于数学常数、配置上限等不变值。

  3. 变量遮蔽是强大工具
    允许重复使用 let 重新绑定同名变量,可用于类型转换或中间计算,比可变变量更安全。

  4. 模式解构增强表达力
    let 支持从元组、结构体、数组中提取字段,简化数据访问流程。

  5. 命名规范与代码风格

    • 变量使用 snake_case
    • 常量使用 SCREAMING_SNAKE_CASE
    • 避免过度使用 mut
  6. 工具辅助提升质量
    使用 rustfmt 自动格式化代码,clippy 检测潜在问题,确保代码符合社区最佳实践。


🛠 常见陷阱与解决方案

问题 错误示例 正确做法
忘记 mut 导致无法修改 let x = 0; x = 1; let mut x = 0; x = 1;
常量缺少类型标注 const MAX: 100; const MAX: i32 = 100;
误以为 const 可在运行时赋值 const NOW: u64 = time::now(); ❌ 不合法,应使用 static 或函数
滥用 mut 导致副作用 多处随意修改同一变量 改用遮蔽或函数式风格

🚀 下一步建议

完成本案例后,建议继续学习:

  • 案例5:基本数据类型操作 —— 掌握整数、浮点数、布尔值等底层类型的使用
  • 案例8:字符串类型(String 与 &str) —— 深入理解 Rust 中字符串的所有权模型
  • 案例16:所有权转移机制 —— 为理解复杂数据结构做好准备

同时推荐阅读官方文档章节:


通过本案例的学习,你已经掌握了 Rust 中最基础但最重要的概念之一——如何安全、高效地管理数据的存储与变更。记住:“默认不可变”不是限制,而是一种保护。正是这种严谨的设计,使得 Rust 能够在没有垃圾回收的前提下,依然保证内存安全与并发安全。

接下来,请继续保持动手实践的习惯,逐步构建属于你的 Rust 知识体系。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐