1. 变量与可变性:Rust的核心设计哲学
1.1 不可变性的设计意义
Rust默认变量不可变,这是其安全模型的基础:
let x = 5;
// x = 6; // 编译错误!不能修改不可变变量
这种设计带来三大优势:
- 线程安全:不可变数据天然线程安全
- 推理简单:变量值在生命周期内保持不变
- 优化空间:编译器可做更多优化
1.2 可变变量(mut)
当确实需要修改变量时使用mut
:
let mut count = 0;
count += 1; // 合法操作
1.3 变量遮蔽(Shadowing)的深层机制
let x = 5;
let x = x + 1; // 新建变量,类型可以改变
let x = "现在我是字符串";
与mut
的关键区别:
- 每次let都创建新绑定(不是修改原变量)
- 可以改变类型
- 原变量被遮蔽但生命周期不变
1.4常量
常量使用 const
关键字声明,必须标注类型:
const MAX_POINTS: u32 = 100_000; // 使用下划线提高可读性
fn main() {
println!("最大分数: {}", MAX_POINTS);
}
常量与不可变变量的区别:
-
常量在全局作用域中有效
-
常量不能使用
mut
-
常量只能设置为常量表达式,不能是函数调用结果或运行时计算值
2. 数据类型
2.1 标量类型详解
整数类型
类型 | 位数 | 范围 | 说明 |
---|---|---|---|
i8 | 8 | -128 到 127 | 8位有符号整数 |
i16 | 16 | -32,768 到 32,767 | 16位有符号整数 |
i32 | 32 | -2,147,483,648 到 2,147,483,647 | 32位有符号整数 |
i64 | 64 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | 64位有符号整数 |
i128 | 128 | -170,141,183,460,469,231,731,687,303,715,884,105,728 到 170,141,183,460,469,231,731,687,303,715,884,105,727 | 128位有符号整数 |
isize | 可变 | 取决于架构(32位或64位系统) | 指针大小的有符号整数 |
u8 | 8 | 0 到 255 | 8位无符号整数 |
u16 | 16 | 0 到 65,535 | 16位无符号整数 |
u32 | 32 | 0 到 4,294,967,295 | 32位无符号整数 |
u64 | 64 | 0 到 18,446,744,073,709,551,615 | 64位无符号整数 |
u128 | 128 | 0 到 340,282,366,920,938,463,463,374,607,431,768,211,455 | 128位无符号整数 |
usize | 可变 | 取决于架构(32位或64位系统) | 指针大小的无符号整数 |
特殊整数操作:
// 溢出处理(debug模式会panic,release模式会wrap)
let x: u8 = 255;
let y = x.wrapping_add(1); // 0
// 安全数学运算
let sum = 1u32.checked_add(2); // Some(3)
let overflow = 255u8.checked_add(1); // None
浮点类型实现细节
Rust采用IEEE-754标准:
f32
:单精度(约6-9位有效数字)f64
:双精度(约15-17位有效数字)
特殊浮点值:
let inf = f32::INFINITY;
let neg_inf = f32::NEG_INFINITY;
let nan = f32::NAN; // 非数字
字符类型
- 4字节存储
- Unicode标量值(U+0000到U+D7FF和U+E000到U+10FFFF)
let emoji = '😻'; // 有效
let c = '\u{1F408}'; // 猫符号
布尔类型
bool
:值为true
或false
let is_true: bool = true;
let is_false: bool = false;
2.2 复合类型内存布局
元组的内存排列
let tup: (i32, f64, u8) = (500, 6.4, 1);
内存布局(64位系统):
+--------+--------+--------+
| i32(4) | pad(4) | f64(8) | u8(1) | pad(7) |
+--------+--------+--------+-------+--------+
数组的栈分配特性
let arr: [i32; 5] = [1, 2, 3, 4, 5]; // 完全在栈上分配
与切片的关系:
let slice: &[i32] = &arr[1..3]; // 借用数组部分元素
3. 控制流:Rust的表达式的力量
3.1 if表达式的类型系统要求
let number = if condition { 5 } else { 6 }; // 分支必须类型相同
// let bad = if true { 5 } else { "six" }; // 编译错误!
3.2 循环控制流详解
loop的独特优势
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // 可返回值
}
};
while循环的编译优化
while condition {
// 编译器会尝试将条件提升到循环外
}
for循环的迭代器协议
for i in 1..=5 { // RangeInclusive
println!("{}", i);
}
// 等价于
let mut iter = (1..=5).into_iter();
while let Some(i) = iter.next() {
println!("{}", i);
}
3.3 模式匹配的编译优化
match value {
1..=5 => println!("1到5"),
6 | 7 => println!("6或7"),
_ => println!("其他"),
}
编译器会:
- 检查匹配的穷尽性
- 优化为跳转表或条件判断
- 保证所有路径都有返回值
4. 深入案例:构建温度转换CLI
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 3 {
eprintln!("用法: {} [C|F] 温度值", args[0]);
std::process::exit(1);
}
let scale = &args[1];
let temperature: f64 = match args[2].parse() {
Ok(t) => t,
Err(_) => {
eprintln!("错误: 温度必须是数字");
std::process::exit(1);
}
};
match scale.as_str() {
"C" => println!("{}°C = {}°F", temperature, celsius_to_fahrenheit(temperature)),
"F" => println!("{}°F = {}°C", temperature, fahrenheit_to_celsius(temperature)),
_ => eprintln!("错误: 请指定C或F作为温度单位"),
}
}
fn celsius_to_fahrenheit(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
fn fahrenheit_to_celsius(f: f64) -> f64 {
(f - 32.0) * 5.0 / 9.0
}
运行程序:
cargo run C 25.5
# 输出
# 25.5°C = 77.9°F
cargo run F 100
# 输出
# 100°F = 37.77777777777778°C
5. 性能考量与最佳实践
整数选择原则:
- 默认使用
i32
(即使在64位系统) - 集合索引使用
usize
- 明确需求时再选择特定大小
浮点注意事项:
// 避免直接比较浮点数
fn approx_equal(a: f64, b: f64) -> bool {
(a - b).abs() < f64::EPSILON
}
循环优化技巧:
// 使用for比while更易优化
for i in 0..collection.len() {
// 编译器知道边界不会改变
}
模式匹配的穷尽检查:
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value(coin: Coin) -> u8 {
match coin { // 如果漏掉某个变体会编译错误
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
6. 常见陷阱与解决方案
问题1:整数溢出
// 不好的做法
let x: u8 = 255;
let y = x + 1; // debug模式panic!
// 正确做法
let y = x.wrapping_add(1); // 明确处理溢出
问题2:浮点比较
// 错误方式
if 0.1 + 0.2 == 0.3 { /* 可能不成立 */ }
// 正确方式
if (0.1 + 0.2 - 0.3).abs() < f64::EPSILON { /* */ }
问题3:数组越界
let arr = [1, 2, 3];
// let x = arr[3]; // 编译时已知越界会报错
// 运行时检查
if let Some(val) = arr.get(3) { // 返回Option
// 安全访问
}
7. 进阶话题预览
- 类型推断算法:Hindley-Milner的Rust实现
- 模式匹配的穷尽性检查:编译器如何验证
- 控制流图(CFG)优化:Rust编译器的处理流程
- LLVM后端优化:如何生成高效机器码
总结回顾
本章深入探讨了:
- Rust变量系统的设计哲学
- 类型系统的内存布局细节
- 控制流表达式的编译行为
- 实际开发中的最佳实践
关键思维转变:
- 从"变量可变"到"默认不可变+显式可变"
- 从"运行时检查"到"编译时验证"
- 从"语句导向"到"表达式优先"
在接下来的章节中,我们将探索Rust函数与模块系统,学习如何组织更复杂的代码结构。届时会看到这些基础概念如何支撑起更高级的语言特性。
案例源代码:https://pan.baidu.com/s/1TvNjvzGFhZb8Idq1oCQGYA?pwd=4z1j 提取码: 4z1j