📚 Rust 课程系列

  1. 课程概览
  2. 基础语法(本文)
  3. 函数与输入输出
  4. 所有权、借用与生命周期
  5. 结构体、枚举与模式匹配
  6. 类型系统:泛型、trait 与多态
  7. 集合与容器
  8. 错误处理与 Panic 恢复
  9. 模块、属性与宏
  10. 智能指针、迭代器与闭包
  11. 并发与异步编程
  12. Unsafe Rust 与常用 trait 详解
  13. 工具链、Cargo 与外部 crate
  14. 最佳实践、性能与调试

Rust 是一门注重内存安全、性能和并发性的系统编程语言。本篇是《Rust 课程》系列的第一篇,从最基础的变量与常量、数据类型、Range 语法到控制流,带你建立对 Rust 语法骨架的初步认识。后续章节将在所有权、类型系统、并发等方向逐步深入。

变量与常量

声明方式可变性类型标注作用域典型用途
let x = 5;不可变(默认)可推断块级一般绑定
let mut y = 10;可变可推断块级需要修改的值
const MAX: u32 = 100;不可变必须标注可全局编译期常量
static G: &str = "hi";不可变(默认)必须标注全局静态生命周期值

💡 提示:Rust 变量默认不可变——这是与 C++/Go/Java 最大的区别之一。不可变意味着编译器可以做出更多优化,也从根本上杜绝了"意外修改"类 bug。需要修改时显式写 mut,让意图一目了然。

遮蔽(Shadowing)

1
2
3
4
5
6
7
let x = 5;
let x = x + 1; // 遮蔽:创建新变量,可改变类型
let x = "hello"; // 合法!遮蔽允许类型变化

let mut y = 10;
y = 15; // 可变赋值:类型不能变
// y = "hi"; // 错误!mut 不能改变类型

🔄 对比:遮蔽 ≠ 可变赋值。遮蔽创建新绑定(可改变类型),mut 修改现有绑定(类型固定)。C++ 中同名变量在同级作用域会报错,Rust 则允许遮蔽。

⚠️ 注意:遮蔽常用于值转换链(如 let s = s.trim();),但过度使用会降低可读性。同一块内遮蔽超过 2-3 次时应考虑换用更有意义的变量名。

常量与静态变量

1
2
const MAX_VALUE: u32 = 100_000;        // 编译期求值,内联到使用处
static GREETING: &str = "Hello, Rust!"; // 固定内存地址,全局唯一

⚠️ 注意static mut 是 unsafe 的——多线程并发访问会导致数据竞争。需要全局可变状态时,使用 Atomic* 类型或 Mutex 封装,也可用 once_cell::sync::Lazy 做延迟初始化。

类型别名与字面量后缀

1
2
3
4
5
6
type Kilometers = i32;                          // 类型别名
type MyResult<T> = std::result::Result<T, String>;

let x = 42u8; // 后缀指定类型
let y = 3.14f32;
let z = 100_000i64; // 下划线提高可读性

数据类型

标量类型速查

类别类型备注
有符号整数i8 i16 i32 i64 i128默认推断为 i32
无符号整数u8 u16 u32 u64 u128字节操作常用 u8
架构相关isize / usize与指针同宽,索引/长度用 usize
浮点数f32 f64默认推断为 f64
布尔booltrue / false,占 1 字节
字符charUnicode 标量值,占 4 字节

💡 提示char 是 Unicode 标量值(4 字节),不是 C 的 1 字节 char'😊' 是合法的 char,但字符串内部是 UTF-8 编码,一个 Unicode 字符可能占 1-4 字节——按字节切片可能截断字符。

复合类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 元组:固定长度,异构
let tup: (i32, f64, char) = (500, 6.4, 'z');
let (x, y, z) = tup; // 解构
let first = tup.0; // 索引访问

// 数组:固定长度,同构,栈分配
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 100]; // [0, 0, ..., 0] 重复初始化

// Vec:动态数组,堆分配
let mut v = vec![1, 2, 3];
v.push(4);

// 切片:对连续序列的借用视图,不拥有数据
let slice: &[i32] = &arr[1..4]; // [2, 3, 4]
let hello: &str = &s[0..5]; // 字符串切片

🔄 对比:切片 &[T] / &str 类似 Go 的 slice header(指针+长度),但不包含容量信息。Rust 切片是纯"视图",不能直接 append。

⚠️ 注意:处理 UTF-8 字符串时不要盲目按字节切片(&s[0..n]),可能截断多字节字符。使用 char 迭代或 s.get(..n) 做安全检查。

💡 提示:函数参数优先接受 &[T]&str 而非 &Vec<T>&String——切片更通用,可接收数组、Vec 的引用,避免拷贝。空切片 &[] 合法,可用于表示"无数据"而无需 Option。数组 vs Vec 的选择:长度固定且小用数组(栈分配,零开销),需要动态增长用 Vec(考虑 with_capacity 减少重分配)。

字符串与类型转换

1
2
3
4
5
6
7
8
9
let s1 = String::from("hello");   // 堆分配,可增长
let s2: &str = "world"; // 字面量,不可变借用
let s3 = s1 + " " + s2; // String 拼接(s1 被消费)
let len = s3.len(); // 字节长度,非字符数!

// 类型转换
let num: i32 = "42".parse().unwrap(); // &str → 数字
let s = num.to_string(); // 数字 → String
let f = num as f64; // 数值类型间 as 转换

⚠️ 注意as 转换在数值截断时静默发生(如 300i32 as u844)。安全转换请用 try_into()From/Into trait。

Range(范围)语法

Range 是 Rust 中用于表示序列范围的重要语法,广泛应用于循环迭代、切片操作和模式匹配中。

基本语法

1
2
for i in 1..5 { }   // 半开区间:1, 2, 3, 4
for i in 1..=5 { } // 闭区间:1, 2, 3, 4, 5

切片中的 Range

1
2
3
4
let arr = [10, 20, 30, 40, 50];
let a = &arr[..3]; // 从起点:[10, 20, 30]
let b = &arr[2..]; // 到终点:[30, 40, 50]
let c = &arr[..]; // 全范围:[10, 20, 30, 40, 50]

Range 类型

语法类型说明
start..endRange<T>半开区间
start..=endRangeInclusive<T>闭区间
start..RangeFrom<T>从 start 到无限
..endRangeTo<T>从起点到 end
..=endRangeToInclusive<T>从起点到 end(包含)
..RangeFull全范围
1
2
3
4
5
6
7
8
use std::ops::{Range, RangeInclusive, RangeFrom, RangeTo, RangeToInclusive, RangeFull};

let r1: Range<i32> = 1..5;
let r2: RangeInclusive<i32> = 1..=5;
let r3: RangeFrom<i32> = 3..;
let r4: RangeTo<i32> = ..5;
let r5: RangeToInclusive<i32> = ..=5;
let r6: RangeFull = ..;

Range 的迭代与方法

1
2
3
4
(1..=5).rev()              // 倒序:5, 4, 3, 2, 1
(0..10).step_by(2) // 步长:0, 2, 4, 6, 8
let sum: i32 = (1..=100).sum(); // 求和:5050
let vec: Vec<i32> = (1..5).collect(); // 转集合

Range 在模式匹配中的应用

1
2
3
4
5
6
let x = 5;
match x {
1..=10 => println!("1到10"),
11..=100 => println!("11到100"),
_ => println!("其他"),
}

⚠️ 注意:切片越界会 panic,用 get() 安全访问:arr.get(1..5) 返回 Option。半开区间 start >= end 时 range 为空,循环体不执行。浮点数 range 只有闭区间 ..= 可迭代,但不推荐(精度问题)。

Range 与切片语法(进阶)

🔬 进阶:Range 类型实现了 SliceIndex trait 的类型都可以用作 [] 索引参数——Range 类型正是如此。自定义类型只要实现 SliceIndex,也能用于切片索引,这是 Rust 索引语法可扩展性的基础。

控制流

条件表达式

1
2
// if 是表达式,可直接赋值——无需三元运算符
let result = if number > 0 { "positive" } else { "non-positive" };

💡 提示:条件必须是 bool,Rust 不会像 C/C++ 那样将整数隐式转为布尔值。if 1 { } 是编译错误。

循环

1
2
3
4
5
6
7
8
9
10
11
// loop:无限循环,break 可返回值
let result = loop {
counter += 1;
if counter == 10 { break counter * 2; }
};

// while:条件循环
while counter < 15 { counter += 1; }

// for:迭代器循环(最常用)
for i in 0..5 { println!("i: {}", i); }

💡 提示:优先使用 for + 迭代器,它比 while 索引更安全(不会越界)且更符合 Rust 惯例。loop + break 返回值适合复杂退出条件且需返回计算结果的场景。

⚠️ 注意:迭代方式的选择影响所有权——iter() 借用、iter_mut() 可变借用、into_iter() 消费所有权。选错会导致编译错误,详见所有权、借用与生命周期

标签循环

1
2
3
4
5
6
'outer: for i in 0..3 {
for j in 0..3 {
if i == 1 && j == 1 { break 'outer; }
println!("({}, {})", i, j);
}
}

🔄 对比:Rust 的标签语法 'label: 类似 Go 的 label:,但 Rust 标签只能与 break/continue 配合使用,不能像 Go 那样 goto

模式匹配在控制流中的应用

1
2
3
4
5
6
let pair = (0, -2);
match pair {
(0, y) => println!("x=0, y={}", y),
(x, 0) => println!("x={}, y=0", x),
_ => println!("其他情况"),
}

💡 提示match 是 Rust 最强大的控制流构造,支持解构、守卫、范围模式等。完整的模式匹配详解见结构体、枚举与模式匹配