Rust: Rust 变量与数据类型:从 let 到类型系统全解
最后更新:2026-08-26
在 Rust 中,变量默认不可变——这不是限制,而是一种安全承诺。
Rust 的变量和类型系统是它内存安全承诺的基石。理解不可变性、类型推断和标量类型,是掌握 Rust 的第一步。
1. 你将学到
- 用
let声明变量,理解不可变性 - 用
let mut创建可变变量 - 用
const定义编译时常量 - Rust 的四种标量类型:整数、浮点、布尔、字符
- 类型推断与显式类型标注
- 变量遮蔽(shadowing)机制
2. 一个收银员的真实故事
(1) 痛苦:Excel 里改错数字
Lisa 是一家小超市的收银员。每天下班她要用 Excel 统计当天的销售额:
- 售价 一直在变(促销、折扣、会员价),她必须手动修改单元格
- 有一次她不小心把一行公式覆盖成了数字,整张表全错
- 重新对账花了 3 个小时
她后来学了 Rust 之后感叹:"如果 Excel 的单元格默认不可修改,我就不用担心覆盖公式了。"
(2) Rust 的变量模型
fn main() {
let total_sales: f64 = 1280.50; // 默认不可变——今天的销售额
// total_sales = 1500.00; // ❌ 编译错误:不可以修改不可变变量!
let mut daily_changes = 0.0; // 用 mut 声明可变变量
daily_changes = 150.0; // ✅ 可以修改
daily_changes = daily_changes + 200.0;
const TAX_RATE: f64 = 0.08; // 编译时常量,全局固定
println!("税率: {}, 今日应收: {}", TAX_RATE, total_sales * (1.0 + TAX_RATE));
}
Rust 的哲学是:能不变就不要变。默认不可变可以让代码更容易推理,减少意外修改带来的 bug。
3. 变量声明
(1) let:默认不可变
let x = 5; // 不可变变量
// x = 10; // ❌ 编译错误:cannot assign twice to immutable variable
(2) let mut:可变变量
let mut y = 5; // 可变变量
y = 10; // ✅ 可以重新赋值
(3) const:编译时常量
const MAX_POINTS: u32 = 100_000; // 必须标注类型
const PI: f64 = 3.1415926535;
| 特性 | let |
let mut |
const |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 不可变 |
| 运行时存在 | 是 | 是 | 否(编译期内联) |
| 类型标注 | 可省略 | 可省略 | 必须 |
| 全局作用域 | 否 | 否 | 是 |
| 表达式计算 | 运行时 | 运行时 | 编译期常量 |
4. 标量类型
Rust 有四种基本标量类型:整数、浮点、布尔、字符。
graph TB
A[标量类型] --> B[整数: i8/u8/i16/u16/i32/u32/i64/u64/i128/u128]
A --> C[浮点: f32/f64]
A --> D[布尔: bool]
A --> E[字符: char]
(1) 整数类型
| 长度 | 有符号 | 无符号 | 范围 |
|---|---|---|---|
| 8-bit | i8 |
u8 |
-128 ~ 127 / 0 ~ 255 |
| 16-bit | i16 |
u16 |
-32768 ~ 32767 / 0 ~ 65535 |
| 32-bit | i32 |
u32 |
±21 亿 / 0 ~ 42 亿 |
| 64-bit | i64 |
u64 |
±9.2 × 10¹⁸ / 0 ~ 1.8 × 10¹⁹ |
| 128-bit | i128 |
u128 |
极大范围 |
| arch | isize |
usize |
与系统位数相同(64 位系统 = i64/u64) |
默认整数类型是
i32——性能最佳且够用。
(2) 浮点类型
let a: f32 = 3.14; // 32 位浮点(单精度)
let b: f64 = 3.141592653589793; // 64 位浮点(双精度,默认类型)
默认浮点类型是
f64——现代 CPU 上 f64 和 f32 速度几乎一样,精度更高。
(3) 布尔类型
let is_ok: bool = true;
let is_not = false;
布尔值常用于条件判断,如 if 和 while 的条件表达式。
(4) 字符类型
let c: char = 'A'; // ASCII 字符
let emoji: char = '🦀'; // Unicode 字符(4 字节)
let han: char = '锈'; // 中文汉字
Rust 的
char是 Unicode 标量值,占 4 字节。不是 ASCII!这让 Rust 天然支持国际化。
| 类型 | 大小 | 取值范围 | 典型用途 |
|---|---|---|---|
bool |
1 字节 | true / false |
条件判断、标志位 |
char |
4 字节 | Unicode 标量值 | 单个字符处理 |
f32 |
4 字节 | ±3.4×10³⁸(约 7 位精度) | 图形计算、GPU 着色器 |
f64 |
8 字节 | ±1.8×10³⁰⁸(约 15 位精度) | 科学计算(默认浮点) |
i32 |
4 字节 | ±21 亿 | 通用整数(默认) |
u8 |
1 字节 | 0 ~ 255 | 字节数据、RGB 值 |
usize |
arch | 与系统位数相同 | 数组索引、容器大小 |
5. 类型推断与标注
▶ 示例 1:类型推断(难度 ⭐)
// ============================================
// Rust 编译器根据赋值推断类型
// ============================================
fn main() {
let x = 42; // 推断为 i32(默认整数类型)
let y = 3.14; // 推断为 f64(默认浮点类型)
let z = true; // 推断为 bool
let c = 'R'; // 推断为 char
// 可以用 :type 查看(本节示例,不是标准方法)
println!("x 的值是: {}", x);
println!("y 的值是: {}", y);
}
输出:
x 的值是: 42
y 的值是: 3.14
编译器会根据上下文自动推断类型,开发者不需要在每一处都写类型标注。
▶ 示例 2:显式类型标注(难度 ⭐)
// ============================================
// 显式指定类型——当需要特定大小或精度时
// ============================================
fn main() {
let small: u8 = 255; // u8 最大值
let big: i64 = 9_223_372_036_854_775_807; // 下划线可作数字分隔符
let precise: f32 = 3.141592653589793; // f32 会截断精度
let flag: bool = 5 > 3; // 布尔表达式的结果
println!("small: {}, big: {}, precise: {:.10}, flag: {}", small, big, precise, flag);
}
输出:
small: 255, big: 9223372036854775807, precise: 3.1415927410, flag: true
注意
f32的精度只有约 7 位有效数字,3.141592653589793被截断为3.1415927410。
▶ 示例 3:变量遮蔽(Shadowing)(难度 ⭐⭐)
// ============================================
// Shadowing:用同一个名称覆盖之前的变量
// ============================================
fn main() {
let x = 5; // 第一个 x
let x = x + 1; // 遮蔽:第二个 x,值变成 6
let x = x * 2; // 遮蔽:第三个 x,值变成 12
// 遮蔽允许改变类型!
let name = "Rust"; // &str 类型
let name = name.len(); // 变成了 usize 类型!
println!("x 的值: {}", x);
println!("name 的长度: {}", name);
}
输出:
x 的值: 12
name 的长度: 4
遮蔽 ≠ 可变。 遮蔽创建了一个新变量(新内存),而
mut是修改同一块内存。遮蔽可以在同一作用域改变变量类型,mut不行。
▶ 示例 4:综合练习——库存管理小工具(难度 ⭐⭐⭐)
// ============================================
// 综合示例:用变量、遮蔽和常量模拟库存管理
// 演示 let/mut/const/shadowing 的实际应用
// ============================================
const MAX_STOCK: u32 = 10_000;
const DISCOUNT_THRESHOLD: u32 = 500;
fn main() {
let item_name = "Wireless Mouse";
let price: f64 = 49.99;
let mut stock: u32 = 2500;
println!("=== 库存管理系统 ===");
println!("商品: {}", item_name);
println!("单价: {:.2} 元", price);
println!("库存: {} 件", stock);
stock = stock - 300;
println!("\n--- 今日出货 300 件 ---");
println!("剩余库存: {} 件", stock);
let price = price * 0.85;
println!("\n--- 会员折扣 85 折 ---");
println!("会员价: {:.2} 元", price);
let price = format!("{:.2} 元", price);
println!("价格标签: {}", price);
let total_value = (stock as f64) * 49.99;
println!("\n库存总价值: {:.2} 元", total_value);
if stock > DISCOUNT_THRESHOLD {
println!("库存充足(> {}),无需补货", DISCOUNT_THRESHOLD);
} else {
let needed = MAX_STOCK - stock;
println!("库存不足!建议补货 {} 件", needed);
}
println!("最大容量: {} 件", MAX_STOCK);
}
输出:
=== 库存管理系统 ===
商品: Wireless Mouse
单价: 49.99 元
库存: 2500 件
--- 今日出货 300 件 ---
剩余库存: 2200 件
--- 会员折扣 85 折 ---
会员价: 42.49 元
价格标签: 42.49 元
库存总价值: 109978.00 元
库存充足(> 500),无需补货
最大容量: 10000 件
此示例综合运用了
const常量、let不可变变量、let mut可变变量和变量遮蔽(遮蔽改变类型:f64→String),展示了实际场景中各变量声明方式的配合使用。
▶ 示例 5:整数溢出与安全运算(难度 ⭐⭐)
// ============================================
// 整数溢出行为:debug 模式 panic vs release 模式回绕
// ============================================
fn main() {
let max_u8: u8 = 255;
println!("u8 最大值: {}", max_u8);
// 安全运算方法(不会 panic,返回溢出结果)
let (result, did_overflow) = max_u8.overflowing_add(1);
println!("255 + 1 (overflowing_add): {},是否溢出: {}", result, did_overflow);
let checked = max_u8.checked_add(1);
println!("255 + 1 (checked_add): {:?}", checked);
let saturating = max_u8.saturating_add(100);
println!("255 + 100 (saturating_add): {}", saturating);
let wrapping: u8 = max_u8.wrapping_add(1);
println!("255 + 1 (wrapping_add): {}", wrapping);
let neg_i8: i8 = -128;
let (abs_result, overflow) = neg_i8.overflowing_abs();
println!("i8 最小值取绝对值: {},是否溢出: {}", abs_result, overflow);
}
输出:
u8 最大值: 255
255 + 1 (overflowing_add): 0,是否溢出: true
255 + 1 (checked_add): None
255 + 100 (saturating_add): 255
255 + 1 (wrapping_add): 0
i8 最小值取绝对值: -128,是否溢出: true
Rust 在 debug 模式下整数溢出会 panic,release 模式会回绕(wrap around)。推荐使用
checked_*、saturating_*、wrapping_*系列方法来显式控制溢出行为。
▶ 示例 6:变量遮蔽与类型转换实战(难度 ⭐⭐⭐)
// ============================================
// 变量遮蔽实战:字符串解析链
// ============================================
fn main() {
let input = "42.5";
println!("原始输入: {} (类型: &str)", input);
let input = input.parse::<f64>();
println!("解析结果: {:?}", input);
let input = match input {
Ok(value) => value,
Err(_) => 0.0,
};
println!("解包后: {} (类型: f64)", input);
let input = input as i32;
println!("转整数: {} (类型: i32,小数截断)", input);
let input = input * 2;
println!("翻倍后: {} (类型: i32)", input);
let input = format!("结果: {}", input);
println!("最终输出: {} (类型: String)", input);
let alice_score = "95";
let alice_score = alice_score.parse::<u32>().unwrap_or(0);
let alice_grade = if alice_score >= 90 { "A" } else { "B" };
println!("Alice: 分数={}, 等级={}", alice_score, alice_grade);
}
输出:
原始输入: 42.5 (类型: &str)
解析结果: Ok(42.5)
解包后: 42.5 (类型: f64)
转整数: 42 (类型: i32,小数截断)
翻倍后: 84 (类型: i32)
最终输出: 结果: 84 (类型: String)
Alice: 分数=95, 等级=A
变量遮蔽允许在同一个作用域中逐步转换数据类型——从
&str→Result<f64>→f64→i32→String,每一步都用同一个变量名,代码清晰无冗余。
❓ 常见问题
📖 小节
- Rust 变量默认不可变,用
mut声明可变变量 const编译时常量必须标注类型,且命名惯例全大写- 整数类型有符号(i)和无符号(u)之分,默认
i32 - 浮点默认
f64,char占 4 字节支持 Unicode - Rust 编译器有强大的类型推断,但需要时可以显式标注
- 变量遮蔽允许用同名新变量覆盖旧变量,甚至可以改变类型
📝 作业
- 难度 ⭐:声明一个
u16类型的变量并赋值为 65535,然后尝试改成 65536,观察编译器的报错信息。 - 难度 ⭐⭐:写一个程序,用
let mut声明可变变量累计 1 到 5 的和,输出结果。 - 难度 ⭐⭐⭐:尝试在
fn main()外部声明一个const常量并在函数内使用。然后尝试在函数外声明一个let变量,观察编译器报错——体会 const 的全局性。