//! # Rust 完整教学程序(兼容所有 Edition,窗口保持打开)
//!
//! 这个程序用一位冒险者的故事,一次性向你展示 Rust 语言的绝大多数核心知识。
//! 每一行代码都有超级详细的注释:不仅解释逻辑,还解释每个单词(关键字)和
//! 每个符号(如 `{` `}` `;` `:` 等等)的用途,非常适合零基础的同学学习。
// 下面这行 `//` 是单行注释,编译器会忽略它后面的内容。
// 接下来的 `#![...]` 是**内部属性**,它影响整个 crate(也就是当前程序)。
// `allow(...)` 表示允许某些本来会触发警告的代码风格,我们这里暂时放宽限制,
// 以便专心学习语法。
#![allow(unused)]
#![allow(non_snake_case)]
// -------------------- 1. 导入标准库里的工具 --------------------
// `use` 关键字:把别处的名字引入到当前作用域,这样就不用写很长的全路径。
// `std` 是 Rust 标准库(standard library)的缩写。
// `::` 是路径分隔符,相当于“里面的”。
// `fmt` 是格式化输出的模块;`rc`、`cell`、`sync`、`thread` 后面都会用到。
use std::fmt;
use std::rc::Rc; // Rc 是引用计数智能指针,单线程多所有权
use std::cell::RefCell; // RefCell 是单线程内部可变性的容器
use std::sync::{Arc, Mutex}; // `{ }` 可以一次导入多个名字
use std::thread; // thread 是标准线程模块
use std::error::Error; // Error 是所有错误的公共 trait
use std::fs::File; // fs 是文件系统模块,File 表示文件
use std::io::Read; // io 是输入输出,Read trait 提供 read_to_string 等方法
// -------------------- 2. 自定义宏 --------------------
// `macro_rules!` 关键字:用于定义**声明宏**,后面跟着宏的名字。
// 宏是一种代码生成器,可以在编译时根据模式展开成具体代码。
// 这里定义一个 `log!` 宏,功能类似于 `println!`,但会自动加上 `[LOG]` 前缀。
macro_rules! log {
// `$($arg:tt)*` 是宏的模式:
// - `$` 声明一个宏变量
// - `arg` 是变量的名字(可以随便取)
// - `:tt` 表示这个变量匹配**任意单个 token 树**(token tree)
// - `( ... )*` 表示重复零次或多次,类似正则的 `*`
// - 宏体用 `{ }` 包裹,里面的代码会被替换到调用处
($($arg:tt)*) => {
// `println!` 是标准宏,用于在终端输出文字并换行。
// `format!` 会把参数格式化成字符串。
// 注意:这里没有分号,因为宏可能被用在表达式位置(如 `match` 分支),
// 让宏展开后不产生多余的尾随分号,避免 Edition 2024 的硬错误。
println!("[LOG] {}", format!($($arg)*))
}
}
// -------------------- 3. 自定义错误类型 --------------------
// `enum` 关键字:定义**枚举**,列出所有可能的状态或种类。
// `GameError` 是我们自定义的错误枚举。
// `#[derive(Debug)]` 是一个**派生宏**,它会自动为我们生成 `Debug` trait 的实现,
// 这样错误就可以用 `{:?}` 打印调试信息。
#[derive(Debug)]
enum GameError {
// `NoItem` 是一个枚举成员(变体),没有附加数据。
NoItem,
// `FileError(String)` 是一个携带数据的枚举成员,
// 括号里的 `String` 表示这个错误会包含一段描述文字。
FileError(String),
}
// `impl` 关键字:为某个类型实现 trait 或添加方法。
// 这里 `impl fmt::Display for GameError` 表示为 `GameError` 实现 `Display` trait,
// 从而可以用 `{}` 占位符打印出人类可读的信息。
impl fmt::Display for GameError {
// `fn` 关键字:定义函数。
// `fmt` 是函数名,参数 `(&self, f: &mut fmt::Formatter)` 中:
// - `&self` 表示对自身(当前错误实例)的不可变借用
// - `f` 是一个可变的格式化器引用
// `-> fmt::Result` 是返回类型,表示返回一个格式化的结果。
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// `write!` 宏:向格式化器 `f` 写入内容,类似于 `format!` 但不需要生成新字符串。
// `"{:?}"` 是格式化字符串,`:?` 表示使用 Debug 格式打印。
write!(f, "{:?}", self)
}
}
// 实现标准库的 `Error` trait,表明 `GameError` 是一个正式的错误类型。
// `impl Error for GameError` 不要求实现任何方法(`Error` 有默认实现),
// 但必须拥有 `Display` 和 `Debug`,我们前面已经实现了。
impl Error for GameError {}
// -------------------- 4. 物品枚举 --------------------
// 再定义一个枚举 `Item`,表示游戏中的各种物品。
// 三个变体:
// - `Sword(u32)` 剑,携带攻击力(`u32` 是 32 位无符号整数类型)
// - `Potion(u32)` 药水,携带回复量
// - `Key` 钥匙,不携带数据
#[derive(Debug, Clone, PartialEq)]
enum Item {
Sword(u32),
Potion(u32),
Key,
}
// 为 `Item` 实现 `Display`,让它可以用 `{}` 打印。
impl fmt::Display for Item {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// `match` 关键字:**模式匹配**,会根据 `self` 的不同变体选择执行分支。
match self {
// `Item::Sword(atk)` 模式:
// - `Item::Sword` 匹配剑
// - `atk` 绑定括号里的攻击力
// `=>` 分隔模式与执行表达式。
Item::Sword(atk) => write!(f, "剑(攻{})", atk),
Item::Potion(heal) => write!(f, "药水(回{})", heal),
// `_` 是通配符,匹配任何值,这里用于 `Key`。
_ => write!(f, "钥匙"),
}
}
}
// -------------------- 5. 行为接口(trait) --------------------
// `trait` 关键字:定义共享行为,类似于其他语言中的接口。
// `Attack` trait 要求实现一个方法 `attack_power`,返回攻击力。
trait Attack {
// `fn attack_power(&self) -> u32;` 是方法签名,以分号结尾,没有函数体。
fn attack_power(&self) -> u32;
}
// `Defend` trait 要求实现防御方法,接收伤害值,返回最终实际扣除的生命值。
trait Defend {
fn defend(&self, damage: u32) -> u32;
}
// -------------------- 6. 冒险者结构体 --------------------
// `struct` 关键字:定义**结构体**,将多个相关的值组合成一个新类型。
// `Adventurer<'a>` 有一个**生命周期参数** `'a`。
// - `'a` 是生命周期标记(tick-a,读作“生命周期 a”)
// - 它表示结构体内部的 `name` 字段是一个字符串切片引用 `&'a str`,
// 这个引用的生命周期至少和 `'a` 一样长。
#[derive(Debug)]
struct Adventurer<'a> {
// `name` 字段:`&'a str` 是“生命周期为 `'a` 的字符串切片引用”
// 它只是借用了外部的字符串,并不拥有它。
name: &'a str,
// `health` 字段:`u32` 类型,无符号 32 位整数,生命值。
health: u32,
// `inventory` 字段:`Vec<Item>` 是一个动态数组,里面存放 `Item` 类型的元素。
inventory: Vec<Item>,
}
// 为 `Adventurer<'a>` 实现方法(注意 `impl` 后必须带上生命周期参数 `'a`)。
impl<'a> Adventurer<'a> {
// `new` 是构造函数,参数 `name` 是一个生命周期为 `'a` 的字符串切片。
// 返回 `Self`,这里 `Self` 是 `Adventurer<'a>` 的别名。
fn new(name: &'a str) -> Self {
// 表达式创建结构体实例,字段名和变量名相同时可以简写。
Adventurer {
name, // 等价于 name: name
health: 100,
inventory: Vec::new(), // `Vec::new()` 调用 Vec 的关联函数,创建一个空向量
}
}
// `add_item` 方法,`&mut self` 表示对自身实例的可变借用,
// 这样可以在方法内部修改 `self` 的字段。
fn add_item(&mut self, item: Item) {
// `.push(...)` 是 Vec 的方法,把元素添加到末尾。
self.inventory.push(item);
}
// `use_item` 尝试使用背包中第 `index` 个物品,可能失败,返回 `Result`。
fn use_item(&mut self, index: usize) -> Result<Item, GameError> {
// `if` 条件判断:如果索引超出背包长度……
if index >= self.inventory.len() {
// `return` 关键字:提前返回,`Err(...)` 构造一个错误结果。
return Err(GameError::NoItem);
}
// `Ok(...)` 构造一个成功结果,`.remove(index)` 移除并返回该位置的元素。
Ok(self.inventory.remove(index))
}
fn is_alive(&self) -> bool {
// `self.health > 0` 是一个布尔表达式,返回 `true` 或 `false`。
self.health > 0
}
// 返回名字的引用,注意返回值的生命周期 `&str` 被省略规则自动推导为与 `&self` 相同。
fn get_name(&self) -> &str {
self.name
}
}
// 为 `Adventurer` 实现 `Attack` trait。
impl<'a> Attack for Adventurer<'a> {
fn attack_power(&self) -> u32 {
self.inventory
.iter() // `.iter()` 返回一个不可变引用迭代器
.filter_map(|item| { // `filter_map` 同时做过滤和映射
// `if let` 模式匹配:如果 item 是 `Item::Sword(atk)`,取出攻击力
if let Item::Sword(atk) = item {
Some(atk) // `Some` 是 Option 的变体,表示有值
} else {
None // `None` 表示没有值,被 filter_map 丢弃
}
})
.sum() // `.sum()` 将迭代器中所有的 u32 值加总
}
}
// 为 `Adventurer` 实现 `Defend` trait。
impl<'a> Defend for Adventurer<'a> {
fn defend(&self, damage: u32) -> u32 {
// `saturating_sub` 是 u32 的方法,做减法但不会下溢,如果结果小于 0 就取 0。
damage.saturating_sub(5)
}
}
// -------------------- 7. 泛型背包 --------------------
// `Bag<T>` 是一个**泛型结构体**,`T` 是类型参数,可以代表任意类型。
// 这意味着背包既可以放 `String`,也可以放 `Item` 或其他任何东西。
#[derive(Debug)]
struct Bag<T> {
items: Vec<T>,
}
// `impl<T> Bag<T>` 表示为泛型结构体实现方法,`<T>` 必须在 `impl` 后面出现。
impl<T> Bag<T> {
fn new() -> Self {
// `vec![]` 是一个标准宏,创建一个空向量。
Bag { items: vec![] }
}
fn add(&mut self, item: T) {
self.items.push(item);
}
// `get` 返回 `Option<&T>`,即可能包含一个元素的引用,也可能没有(`None`)。
fn get(&self, index: usize) -> Option<&T> {
self.items.get(index)
}
}
// 一个使用了 `where` 子句的泛型函数。
// `where` 关键字用于清晰地列出复杂的 trait 约束。
// 这里的约束:`T: fmt::Debug` 和 `U: fmt::Debug`,要求两个类型都能用 `{:?}` 打印。
fn describe<T, U>(t: &T, u: &U) -> String
where
T: fmt::Debug,
U: fmt::Debug,
{
// `format!` 宏返回一个格式化的字符串。
format!("{:?} 与 {:?}", t, u)
}
// -------------------- 8. 战斗函数 --------------------
// `impl Attack` 是**impl Trait** 语法,表示参数可以是任何实现了 `Attack` 的类型。
// 这和泛型类似,但写法更简洁。
fn battle(attacker: &impl Attack, defender: &impl Defend) {
let dmg = attacker.attack_power(); // 调用 trait 方法
let remain = defender.defend(dmg);
log!("造成 {} 点伤害,穿透 {}", dmg, remain);
}
// -------------------- 9. 错误处理:读取文件 --------------------
// `Result<String, Box<dyn Error>>`:
// - `Result<T, E>` 是标准库的枚举,表示可能失败的操作。
// - 成功时包含一个 `String`,失败时包含一个**可装箱的错误对象**。
// - `Box<dyn Error>`:`Box` 是堆上分配的智能指针,`dyn Error` 表示实现了 `Error` trait 的任何类型。
// `dyn` 关键字表示“动态分发”,因为错误的具体类型在编译时未知。
fn load_config(path: &str) -> Result<String, Box<dyn Error>> {
// `File::open(path)` 打开文件,返回 `Result<File, io::Error>`。
// `.map_err(...)` 如果结果是 `Err`,就把错误映射成另一种错误。
let mut file = File::open(path)
.map_err(|e| GameError::FileError(format!("无法打开: {}", e)))?;
// 上面的 `?` 是**问号运算符**:
// - 如果 `Result` 是 `Ok`,就会解开包裹,把成功值赋给 `file`。
// - 如果是 `Err`,就会立即从当前函数返回这个错误。
let mut contents = String::new();
// `read_to_string` 把文件内容全部读入 `contents` 字符串中。
// 它返回 `Result<usize, io::Error>`,`?` 同样传播错误。
file.read_to_string(&mut contents)?;
// 函数返回 `Ok(contents)`,即成功时的值。
Ok(contents)
}
// -------------------- 10. 多线程组队 --------------------
fn team_battle() {
// `Arc::new(Mutex::new(vec![]))`:
// - `Vec::new()` 创建一个空向量。
// - `Mutex::new(...)` 创建一个互斥锁,保护内部数据。
// - `Arc::new(...)` 创建一个原子引用计数指针,使多个线程可以共享同一个 `Mutex`。
let team = Arc::new(Mutex::new(vec![]));
// `Arc::clone(&team)` 增加引用计数,产生另一个指向同一数据的指针。
let team2 = Arc::clone(&team);
// `thread::spawn(move || { ... })`:
// - 创建一个新线程,并传递一个**闭包**(匿名函数)。
// - `move` 关键字强制闭包获取它所用到的变量的所有权,
// 在这里 `team2` 的所有权会被移到闭包内,从而可以在新线程中使用。
let handle = thread::spawn(move || {
// `.lock().unwrap()` 获取互斥锁,阻塞直到获得锁,然后返回 `MutexGuard`。
// `unwrap()` 用于处理可能的中毒错误(通常没问题)。
let mut guard = team2.lock().unwrap();
// 通过 `guard` 向团队列表添加一名战友。
guard.push("战友A".to_string());
}); // 闭包和 spawn 调用结束
// `handle.join().unwrap()` 等待新线程结束。
handle.join().unwrap();
// 主线程也获取锁,打印队伍内容。
log!("队伍: {:?}", *team.lock().unwrap());
}
// -------------------- 11. 单线程共享可变状态 --------------------
fn shared_adventurer() {
// `Rc::new(RefCell::new(...))`:
// - `RefCell` 提供内部可变性,允许在拥有不可变引用时修改内部值。
// - `Rc` 提供多所有权,多个 `Rc` 指向同一份堆数据。
let adv = Rc::new(RefCell::new(Adventurer::new("共享者")));
// 克隆 Rc 指针,引用计数变为 2。
let adv2 = Rc::clone(&adv);
// `borrow_mut()` 获取 RefCell 的可变借用(运行时检查),返回 `RefMut`。
// `health -= 10` 修改生命值。
adv2.borrow_mut().health -= 10;
// `borrow()` 获取不可变借用,返回 `Ref`,然后访问字段。
log!("{} 生命: {}", adv.borrow().name, adv.borrow().health);
}
// -------------------- 12. 其他关键字演示 --------------------
// Rust 支持异步编程,关键字为 `async` 和 `await`,但需要 Rust 2018 或更高版本。
// 异步函数定义:`async fn do_something() { ... }`
// 调用异步函数会立即返回一个 `Future`,需要用 `.await` 等待结果。
// 因为异步需要特定的运行时(如 tokio),且基础 rustc 默认 2015 版本不支持,
// 这里只做文字说明。如果你使用 `cargo` 创建项目并设置 `edition = "2021"`,
// 就可以在程序中定义 `async fn` 并调用 `.await`。
// `union` 关键字:定义联合体,多种类型共享同一块内存。
// 访问联合体字段必须在 `unsafe` 块中,因为编译器无法保证安全。
union IntOrFloat {
i: i32, // 32 位有符号整数
f: f32, // 32 位浮点数
}
fn union_demo() {
// 创建联合体实例,只需初始化一个字段。
let u = IntOrFloat { i: 42 };
// `unsafe` 关键字开启不安全块,在这里可以进行一些编译器不保证安全的操作。
unsafe {
log!("union i = {}", u.i);
}
}
// `mod` 关键字:定义模块,用于组织代码。
mod inner {
// `pub fn` 表示这个函数是公开的,外部可以通过模块路径调用。
pub fn helper() {}
pub fn call_parent() {
// 调用父模块中定义的 `log!` 宏,宏是全局可见的,不需要 `super::`。
log!("使用父模块的宏 log!");
}
}
// -------------------- 13. 主函数(程序入口) --------------------
fn main() -> Result<(), Box<dyn Error>> {
log!("=== 冒险开始 ===");
// ---- 变量绑定 ----
// `let` 声明变量,默认不可变。
// `mut` 关键字让变量可以被修改。
let mut hero = Adventurer::new("千字文");
// `_level` 变量名以 `_` 开头,告诉编译器“我知道这个变量没用,别警告我”。
let _level: u32 = 1; // `: u32` 是类型标注
let secret = Some("宝藏"); // `Some` 是 Option 的变体,Option 代表“可能有一个值”
// 给英雄背包添加物品
hero.add_item(Item::Sword(30));
hero.add_item(Item::Potion(20));
hero.add_item(Item::Key);
// 泛型背包的使用:这里 T 被具体化为 `String`。
let mut book_bag: Bag<String> = Bag::new();
book_bag.add("卷轴".to_string()); // `.to_string()` 把字符串字面量转为 String
// 调用方法,判断英雄是否存活
if hero.is_alive() {
// 使用 `{}` 占位符打印字符串,`hero.get_name()` 返回 &str
log!("{} 存活,生命 {}", hero.get_name(), hero.health);
}
// ---- 模式匹配 ----
// `match` 对 `hero.use_item(0)` 的结果进行穷举匹配。
match hero.use_item(0) {
Ok(item) => log!("使用物品: {}", item), // 成功分支
Err(e) => log!("错误: {}", e), // 失败分支
}
// `if let` 简洁写法:只匹配 `Some`,忽略其他情况。
if let Some(treasure) = secret {
log!("发现: {}", treasure);
}
// `ref` 关键字:在模式中绑定引用,不移动所有权。
if let Some(ref _first) = hero.inventory.first() {
log!("背包第一件物品存在");
}
// ---- 循环 ----
let mut cnt = 0;
loop { // `loop` 无限循环
cnt += 1;
if cnt > 2 { break; } // `break` 跳出循环
log!("loop 循环 {}", cnt);
}
while cnt < 5 { // `while` 条件循环
cnt += 1;
if cnt == 4 { continue; } // `continue` 跳过本次循环剩余部分,进入下一次迭代
log!("while 循环 {}", cnt);
}
// `for ... in ...` 迭代器循环,`0..2` 是范围,左闭右开。
for i in 0..2 {
log!("for 范围 {}", i);
}
// ---- 迭代器与闭包 ----
let items = vec![Item::Sword(10), Item::Potion(5)];
let strong: Vec<_> = items.iter()
.filter(|it| matches!(it, Item::Sword(a) if *a > 5))
.collect(); // `collect()` 把迭代器转换为集合
log!("强力物品: {:?}", strong);
// 闭包:`|amount: u32| { ... }` 定义匿名函数,可以捕获环境。
let heal = |amount: u32| {
log!("闭包回复 {} 点", amount);
};
heal(15); // 调用闭包
// `move` 闭包:强制获得 `name_str` 的所有权。
let name_str = String::from("闭包捕获");
let print_name = move || {
log!("{}", name_str);
};
print_name();
// 这里 `name_str` 已被移动,不能再使用。
// ---- trait 与泛型 ----
let dragon = Adventurer::new("恶龙");
battle(&hero, &dragon);
log!("{}", describe(&hero, &dragon));
// ---- 生命周期 ----
let name_ref: &str = hero.get_name();
log!("名字引用: {}", name_ref);
// ---- 错误处理 ----
match load_config("adventure.toml") {
Ok(cfg) => log!("配置: {}", cfg),
Err(e) => log!("加载失败: {}", e),
}
// ---- 智能指针与内部可变性 ----
shared_adventurer();
// ---- 多线程 ----
team_battle();
// ---- 其他关键字 ----
// 关于异步的说明已写在前面第 12 节的注释中,这里不再调用异步函数。
union_demo();
inner::call_parent(); // 调用子模块的函数
log!("=== 冒险结束 ===");
// === 防止窗口一闪而过 ===
// `std::io::stdin()` 返回一个标准输入句柄。
// `.read_line(&mut pause)` 读取一行输入,存入 `pause` 字符串中,
// 程序会停在这里等待用户按下 Enter 键,然后才继续往下执行。
// 这样控制台窗口就不会立刻消失了。
log!("按 Enter 键退出...");
let mut pause = String::new();
std::io::stdin().read_line(&mut pause).unwrap();
// `main` 函数返回 `Ok(())` 表示程序正常结束。
Ok(())
}
// -------------------- 14. 测试 --------------------
// `#[cfg(test)]` 条件编译:这个模块只在执行 `cargo test` 时才编译。
#[cfg(test)]
mod tests {
// `use super::*;` 导入父模块的所有公开项。
use super::*;
// `#[test]` 属性标记测试函数。
#[test]
fn test_attack_power() {
let mut hero = Adventurer::new("测试");
hero.add_item(Item::Sword(40));
// `assert_eq!` 宏断言两个值相等,如果不相等程序会 panic。
assert_eq!(hero.attack_power(), 40);
}
#[test]
fn test_use_item_error() {
let mut hero = Adventurer::new("测试");
// `assert!` 宏断言括号内的布尔表达式为 `true`。
assert!(hero.use_item(0).is_err());
}
}