在上一篇文章中,我们完成了环境搭建并跑通了第一个程序。现在,是时候进入 Rust 语言的核心语法了。Rust 的语法设计有一条贯穿始终的主线:默认安全,显式选择。变量默认不可变,需要修改时必须显式声明 mut;类型必须明确,但编译器又足够智能地支持类型推断。本文从变量声明讲起,系统讲解 Rust 的基本类型、复合类型、函数定义和控制流,并通过一个完整的斐波那契数列实战帮你巩固所学。
一、变量与可变性:默认不可变的设计哲学
1.1 let 与 let mut
在 Rust 中,变量默认是不可变的(immutable)。要修改变量的值,必须使用 mut 关键字显式声明:
rust
fn main() {
let x = 5;
println!("x = {}", x);
// x = 6; // ❌ 编译错误:cannot assign twice to immutable variable
let mut y = 5;
y = 6; // ✅ 可以修改
println!("y = {}", y);
}
为什么默认不可变? 这并非随意的设计选择,而是 Rust 的核心理念之一:让编译器帮你发现意外修改。在大型项目中,很多 bug 源于"某个变量在你不期望的地方被修改了"。默认不可变让这种情况在编译期就被捕获。
1.2 变量遮蔽(Shadowing)
Rust 允许用同一个名字声明新变量,覆盖之前的变量------这叫遮蔽:
rust
fn main() {
let x = 5;
let x = x + 1; // 新的 x,值为 6
let x = x * 2; // 新的 x,值为 12
println!("x = {}", x); // 输出 12
}
遮蔽 vs 可变变量的区别:
遮蔽创建了一个新变量,可以改变类型。
mut 修改的是同一个变量,不能改变类型。
rust
let spaces = " "; // &str 类型
let spaces = spaces.len(); // usize 类型(遮蔽允许类型改变)
let mut spaces = " ";
// spaces = spaces.len(); // ❌ 编译错误:类型不匹配
1.3 常量的声明
常量用 const 声明,必须标注类型,且值必须在编译期确定:
rust
const MAX_POINTS: u32 = 100_000;
常量 vs 不可变变量的区别:
常量必须在编译期求值,不可变变量可以在运行时求值。
常量可以声明在全局作用域。
常量命名惯例是 SCREAMING_SNAKE_CASE。
二、基本数据类型
Rust 是静态类型语言,所有类型在编译期确定。但编译器有强大的类型推断能力,通常不需要显式标注。
2.1 整数类型
Rust 提供了多种整数类型,区别在于位数和有无符号:

默认类型是 i32------除非有特殊需求,否则通常使用 i32。
rust
let a = 42; // i32(默认)
let b: u64 = 100; // 显式标注
let c = 1_000_000; // 下划线分隔,便于阅读
let d = 0xff; // 十六进制
let e = 0o77; // 八进制
let f = 0b1010_1010; // 二进制
let g = b'A'; // 字节(仅限 u8)
⚠️ 整数溢出:debug 模式下溢出会 panic,release 模式下会"环绕"(wrap around)。如果需要显式处理溢出,使用 checked_add、wrapping_add 等方法。
2.2 浮点数类型
rust
let x = 2.0; // f64(默认)
let y: f32 = 3.0; // 显式 f32
Rust 的浮点数遵循 IEEE-754 标准。默认 f64,因为现代 CPU 对双精度的处理速度与单精度相当,且精度更高。
2.3 布尔类型
rust
let t = true;
let f: bool = false;
2.4 字符类型
Rust 的 char 是 4 字节的 Unicode 标量值,可以表示任何 Unicode 字符:
rust
let c = 'z';
let z = 'ℤ';
let heart = '❤';
let emoji = '😀';
💡 注意:char 用单引号,字符串 &str 用双引号。这是 Rust 中容易混淆的地方。
三、复合类型
3.1 元组(Tuple)
元组将多个不同类型的值组合在一起,长度固定:
rust
let tup: (i32, f64, char) = (500, 6.4, 'x');
// 解构(destructuring)
let (a, b, c) = tup;
println!("a = {}, b = {}, c = {}", a, b, c);
// 通过索引访问
println!("第一个元素: {}", tup.0);
println!("第二个元素: {}", tup.1);
单元类型 () 是一个特殊的空元组,表示"无返回值",函数无返回值时隐式返回 ()。
3.2 数组(Array)
数组的长度是类型的一部分,编译期确定:
rust
let a = [1, 2, 3, 4, 5];
let b: [i32; 5] = [1, 2, 3, 4, 5];
let c = [3; 5]; // 5 个元素,值都是 3
println!("{}", a[0]); // 通过索引访问
数组越界:Rust 在运行时检查索引,越界会 panic,而不是像 C 一样产生未定义行为。
💡 什么时候用数组 vs Vec? 长度固定、在栈上分配用数组;长度可变、在堆上分配用 Vec。数组适合小规模固定数据,Vec 适合动态数据。
四、函数:Rust 代码的基本单元
4.1 函数定义
rust
fn add(a: i32, b: i32) -> i32 {
a + b // 表达式,作为返回值(注意没有分号)
}
关键规则:
每个参数必须标注类型。
返回类型用 -> 标注,无返回值时省略。
最后一个表达式的值就是返回值,不需要 return(但可以用)。
4.2 表达式 vs 语句
这是 Rust 的核心概念之一:
语句(Statement) :执行操作但不返回值,末尾有分号。
表达式(Expression) :计算并返回一个值,末尾无分号。
rust
fn main() {
let x = 5; // 语句(let 是语句)
let y = {
let z = 3;
z + 1 // 表达式,值为 4
}; // 整个块作为表达式赋值给 y
println!("y = {}", y); // 输出 4
}
函数返回值的常见错误:
rust
// ❌ 错误:加了分号,变成了语句,返回 ()
fn add_wrong(a: i32, b: i32) -> i32 {
a + b;
}
// ✅ 正确:表达式作为返回值
fn add_right(a: i32, b: i32) -> i32 {
a + b
}
4.3 提前返回
使用 return 可以提前结束函数:
rust
fn check_positive(n: i32) -> bool {
if n < 0 {
return false; // 提前返回
}
true // 最后表达式作为返回值
}
五、控制流
5.1 if / else
Rust 的 if 条件是表达式,可以用在 let 语句中:
rust
let condition = true;
let number = if condition { 5 } else { 6 };
⚠️ 注意:if 和 else 分支的返回类型必须相同。
5.2 循环:loop、while、for
loop:无限循环,可以返回值:
rust
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // break 可以返回值
}
};
println!("result = {}", result); // 20
while:带条件的循环:
rust
let mut n = 3;
while n != 0 {
println!("{}!", n);
n -= 1;
}
for:遍历集合,最推荐的循环方式:
rust
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("值: {}", element);
}
// 范围遍历
for n in 1..=5 { // 1 到 5(包含 5)
println!("{}", n);
}
for n in (1..5).rev() { // 4, 3, 2, 1
println!("{}", n);
}
💡 实践建议:Rust 中优先使用 for 循环,而非 while 或 loop。for 循环天然避免了数组越界和手动索引的复杂性。
5.3 标签循环与嵌套控制
rust
'outer: for i in 1..=3 {
for j in 1..=3 {
if i == 2 && j == 2 {
break 'outer; // 跳出外层循环
}
println!("i = {}, j = {}", i, j);
}
}
六、实战:计算斐波那契数列
将本文所学知识整合起来,编写一个计算斐波那契数列的程序:
rust
use std::io;
/// 递归版:简洁但效率低(指数级时间复杂度)
fn fib_recursive(n: u32) -> u64 {
if n <= 1 {
return n as u64;
}
fib_recursive(n - 1) + fib_recursive(n - 2)
}
/// 迭代版:效率高(O(n))
fn fib_iterative(n: u32) -> u64 {
if n <= 1 {
return n as u64;
}
let mut a = 0u64;
let mut b = 1u64;
for _ in 2..=n {
let temp = a + b;
a = b;
b = temp;
}
b
}
fn main() {
println!("请输入斐波那契数列的项数(0-90):");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("读取输入失败");
let n: u32 = match input.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("请输入有效的数字!");
return;
}
};
if n > 90 {
println!("数字太大,u64 会溢出!");
return;
}
println!("迭代版:fib({}) = {}", n, fib_iterative(n));
if n <= 40 {
println!("递归版:fib({}) = {}", n, fib_recursive(n));
} else {
println!("递归版:n > 40 时耗时过长,跳过");
}
}
代码要点:
使用 std::io 读取用户输入。
使用 parse() 将字符串转换为数字,match 处理解析错误。
递归版展示函数递归,迭代版展示 for 循环和变量可变性。
使用 u64 避免 i32 在 n > 46 时溢出。
七、小结
变量默认不可变:let 声明不可变变量,let mut 声明可变变量。
遮蔽(Shadowing) :用同名新变量覆盖旧变量,可以改变类型。
基本类型:整数(默认 i32)、浮点数(默认 f64)、布尔、字符(4 字节 Unicode)。
复合类型:元组(不同类型组合)、数组(同类型固定长度)。
函数:参数必须标注类型,最后表达式作为返回值(不加分号)。
表达式 vs 语句:表达式有返回值,语句无返回值;这是 Rust 的核心概念。
控制流:if 是表达式;loop 可以返回值;for 是最推荐的循环方式。