第12篇:模块与错误处理——组织代码与处理问题 Rust中文编程
·
第12篇:模块与错误处理——组织代码与处理问题
作者: 李金雨
联系方式: wbtm2718@qq.com
目标读者: Rust中文编程
核心理念: AI时代必须使用中文编程,母语编程阅读效率极高
1. 开篇引入
本课目标
- 理解什么是模块
- 掌握模块的创建和使用方法
- 理解模块的访问控制
- 掌握错误处理的基本方法
- 理解Result类型的使用
- 掌握错误传播(?操作符)的使用
生活场景引入
想象一下,你有一个工具箱,里面有很多工具:
- 锤子和螺丝刀放在一个格子里
- 尺子和铅笔放在另一个格子里
- 剪刀和胶水放在第三个格子里
这样,你可以很容易地找到你需要的工具。在编程中,模块就像工具箱的格子,它可以帮助你组织代码,使代码更加清晰。
另外,当你使用工具时,可能会遇到一些问题,比如锤子坏了,或者螺丝刀找不到了。在编程中,错误处理就像处理这些问题,它可以帮助你优雅地处理程序运行时的错误。
预期成果展示
通过这节课的学习,你将能够:
- 创建和使用模块来组织代码
- 理解模块的访问控制
- 使用Result类型处理错误
- 使用?操作符传播错误
- 编写更加健壮的程序
2. 概念讲解
模块系统
什么是模块
模块是Rust中组织代码的方式,它可以将相关的代码放在一起,使代码更加清晰和可维护。
模块的创建
在Rust中,我们使用mod关键字来创建模块:
fn main() {
// 定义一个模块
mod 数学工具 {
pub fn 加法(a: i32, b: i32) -> i32 {
a + b
}
pub fn 减法(a: i32, b: i32) -> i32 {
a - b
}
}
}
模块的访问控制
模块中的项默认是私有的,只有模块内部可以访问。如果要让模块外部也能访问,需要使用pub关键字:
fn main() {
mod 数学工具 {
pub fn 加法(a: i32, b: i32) -> i32 { // 公共函数
a + b
}
fn 减法(a: i32, b: i32) -> i32 { // 私有函数
a - b
}
}
println!("1 + 2 = {}", 数学工具::加法(1, 2)); // 正确:加法是公共的
// println!("3 - 1 = {}", 数学工具::减法(3, 1)); // 错误:减法是私有的
}
模块的嵌套
模块可以嵌套,形成层次结构:
fn main() {
mod 工具包 {
pub mod 数学工具 {
pub fn 加法(a: i32, b: i32) -> i32 {
a + b
}
}
pub mod 字符串工具 {
pub fn 反转(文本: &str) -> String {
文本.chars().rev().collect()
}
}
}
println!("1 + 2 = {}", 工具包::数学工具::加法(1, 2));
println!("反转:{}", 工具包::字符串工具::反转("你好"));
}
使用use关键字
使用use关键字可以简化模块的使用:
fn main() {
mod 工具包 {
pub mod 数学工具 {
pub fn 加法(a: i32, b: i32) -> i32 {
a + b
}
}
}
use 工具包::数学工具::加法;
println!("1 + 2 = {}", 加法(1, 2)); // 直接使用加法,不需要前缀
}
错误处理
Result类型
Rust使用Result<T, E>类型来处理错误,其中T是成功时的类型,E是错误时的类型:
fn main() {
enum Result<T, E> {
Ok(T), // 成功,包含成功值
Err(E), // 失败,包含错误值
}
}
例如:
fn main() {
fn 除法(分子: i32, 分母: i32) -> Result<i32, String> {
if 分母 == 0 {
Err(String::from("除数不能为零"))
} else {
Ok(分子 / 分母)
}
}
match 除法(10, 2) {
Ok(结果) => println!("结果:{}", 结果),
Err(错误) => println!("错误:{}", 错误),
}
match 除法(10, 0) {
Ok(结果) => println!("结果:{}", 结果),
Err(错误) => println!("错误:{}", 错误),
}
}
错误传播(?操作符)
?操作符可以简化错误处理,它会自动将错误传播给调用者:
fn main() {
fn 读取文件() -> Result<String, std::io::Error> {
let 内容 = std::fs::read_to_string("文件.txt")?; // 如果出错,自动返回错误
Ok(内容)
}
match 读取文件() {
Ok(内容) => println!("文件内容:{}", 内容),
Err(错误) => println!("错误:{}", 错误),
}
}
自定义错误类型
你可以定义自己的错误类型:
fn main() {
#[derive(Debug)]
enum 计算错误 {
除数为零,
负数平方根,
}
fn 除法(分子: i32, 分母: i32) -> Result<i32, 计算错误> {
if 分母 == 0 {
Err(计算错误::除数为零)
} else {
Ok(分子 / 分母)
}
}
fn 平方根(数字: f64) -> Result<f64, 计算错误> {
if 数字 < 0.0 {
Err(计算错误::负数平方根)
} else {
Ok(数字.sqrt())
}
}
match 除法(10, 0) {
Ok(结果) => println!("结果:{}", 结果),
Err(错误) => println!("错误:{:?}", 错误),
}
match 平方根(-1.0) {
Ok(结果) => println!("结果:{}", 结果),
Err(错误) => println!("错误:{:?}", 错误),
}
}
panic!宏
panic!宏用于处理不可恢复的错误,它会立即终止程序:
fn main() {
let 年龄 = -5;
if 年龄 < 0 {
panic!("年龄不能为负数");
}
println!("年龄:{}", 年龄);
}
3. 动手实践
基础练习(必做)
-
创建"学生管理"模块:
mod 学生管理 { pub struct 学生 { pub 姓名: String, pub 年龄: i32, pub 成绩: i32, } pub fn 添加学生(学生们: &mut Vec<学生>, 学生: 学生) { 学生们.push(学生); } pub fn 删除学生(学生们: &mut Vec<学生>, 索引: usize) -> Option<学生> { if 索引 < 学生们.len() { Some(学生们.remove(索引)) } else { None } } pub fn 查询学生(学生们: &Vec<学生>, 姓名: &str) -> Option<&学生> { 学生们.iter().find(|学生| 学生.姓名 == 姓名) } } fn main() { use 学生管理::{学生, 添加学生, 删除学生, 查询学生}; let mut 学生们 = vec![]; // 添加学生 添加学生(&mut 学生们, 学生 { 姓名: String::from("张三"), 年龄: 15, 成绩: 95 }); 添加学生(&mut 学生们, 学生 { 姓名: String::from("李四"), 年龄: 14, 成绩: 88 }); // 查询学生 if let Some(学生) = 查询学生(&学生们, "张三") { println!("找到学生:{},年龄:{}岁,成绩:{}分", 学生.姓名, 学生.年龄, 学生.成绩); } // 删除学生 if let Some(学生) = 删除学生(&mut 学生们, 0) { println!("删除学生:{}", 学生.姓名); } // 打印剩余学生 println!("剩余学生:"); for 学生 in &学生们 { println!("{},{}岁,{}分", 学生.姓名, 学生.年龄, 学生.成绩); } } -
创建"文件操作"模块,处理文件读写错误:
mod 文件操作 { use std::fs; use std::io; pub fn 读取文件(路径: &str) -> Result<String, io::Error> { fs::read_to_string(路径) } pub fn 写入文件(路径: &str, 内容: &str) -> Result<(), io::Error> { fs::write(路径, 内容) } } fn main() { use 文件操作::{读取文件, 写入文件}; // 写入文件 match 写入文件("测试.txt", "你好,世界!") { Ok(_) => println!("文件写入成功"), Err(错误) => println!("写入错误:{}", 错误), } // 读取文件 match 读取文件("测试.txt") { Ok(内容) => println!("文件内容:{}", 内容), Err(错误) => println!("读取错误:{}", 错误), } // 读取不存在的文件 match 读取文件("不存在的文件.txt") { Ok(内容) => println!("文件内容:{}", 内容), Err(错误) => println!("读取错误:{}", 错误), } } -
创建"计算器"模块,处理各种计算错误:
mod 计算器 { #[derive(Debug)] pub enum 计算错误 { 除数为零, 负数平方根, } pub fn 除法(分子: f64, 分母: f64) -> Result<f64, 计算错误> { if 分母 == 0.0 { Err(计算错误::除数为零) } else { Ok(分子 / 分母) } } pub fn 平方根(数字: f64) -> Result<f64, 计算错误> { if 数字 < 0.0 { Err(计算错误::负数平方根) } else { Ok(数字.sqrt()) } } } fn main() { use 计算器::{除法, 平方根, 计算错误}; // 测试除法 match 除法(10.0, 2.0) { Ok(结果) => println!("10.0 / 2.0 = {}", 结果), Err(错误) => println!("除法错误:{:?}", 错误), } match 除法(10.0, 0.0) { Ok(结果) => println!("10.0 / 0.0 = {}", 结果), Err(错误) => println!("除法错误:{:?}", 错误), } // 测试平方根 match 平方根(9.0) { Ok(结果) => println!("√9.0 = {}", 结果), Err(错误) => println!("平方根错误:{:?}", 错误), } match 平方根(-1.0) { Ok(结果) => println!("√-1.0 = {}", 结果), Err(错误) => println!("平方根错误:{:?}", 错误), } }
进阶练习(选做)
-
使用?操作符处理错误:
mod 文件操作 { use std::fs; use std::io; pub fn 读取并处理文件(路径: &str) -> Result<String, io::Error> { let 内容 = fs::read_to_string(路径)?; // 错误传播 let 处理后的内容 = 内容.to_uppercase(); Ok(处理后的内容) } } fn main() { use 文件操作::读取并处理文件; match 读取并处理文件("测试.txt") { Ok(内容) => println!("处理后的内容:{}", 内容), Err(错误) => println!("错误:{}", 错误), } } -
创建嵌套模块:
mod 工具包 { pub mod 数学工具 { pub fn 加法(a: i32, b: i32) -> i32 { a + b } pub fn 减法(a: i32, b: i32) -> i32 { a - b } } pub mod 字符串工具 { pub fn 反转(文本: &str) -> String { 文本.chars().rev().collect() } pub fn 统计字数(文本: &str) -> usize { 文本.chars().count() } } } fn main() { use 工具包::数学工具::{加法, 减法}; use 工具包::字符串工具::{反转, 统计字数}; println!("1 + 2 = {}", 加法(1, 2)); println!("5 - 3 = {}", 减法(5, 3)); println!("反转:{}", 反转("你好")); println!("字数:{}", 统计字数("你好,世界!")); }
挑战练习(拓展)
- 创建一个完整的模块系统:
// 主程序 mod 学生管理 { pub mod 数据 { pub struct 学生 { pub 姓名: String, pub 年龄: i32, pub 成绩: i32, } } pub mod 操作 { use super::数据::学生; pub fn 添加学生(学生们: &mut Vec<学生>, 学生: 学生) { 学生们.push(学生); } pub fn 计算平均成绩(学生们: &Vec<学生>) -> f64 { if 学生们.is_empty() { return 0.0; } let mut 总分 = 0; for 学生 in 学生们 { 总分 += 学生.成绩; } 总分 as f64 / 学生们.len() as f64 } } } fn main() { use 学生管理::数据::学生; use 学生管理::操作::{添加学生, 计算平均成绩}; let mut 学生们 = vec![]; 添加学生(&mut 学生们, 学生 { 姓名: String::from("张三"), 年龄: 15, 成绩: 95 }); 添加学生(&mut 学生们, 学生 { 姓名: String::from("李四"), 年龄: 14, 成绩: 88 }); 添加学生(&mut 学生们, 学生 { 姓名: String::from("王五"), 年龄: 15, 成绩: 92 }); println!("学生列表:"); for 学生 in &学生们 { println!("{},{}岁,{}分", 学生.姓名, 学生.年龄, 学生.成绩); } let 平均分 = 计算平均成绩(&学生们); println!("平均成绩:{}分", 平均分); }
4. 知识总结
核心概念回顾
- 模块:组织代码的方式,将相关的代码放在一起
- 访问控制:使用
pub关键字控制模块项的可见性 - 模块嵌套:模块可以嵌套,形成层次结构
- use关键字:简化模块的使用
- 错误处理:处理程序运行时的错误
- Result类型:表示可能失败的操作
- ?操作符:简化错误传播
- 自定义错误类型:定义自己的错误类型
- panic!宏:处理不可恢复的错误
关键代码速查
| 功能 | 代码 | 说明 |
|---|---|---|
| 模块定义 | mod 模块名 { 代码 } | 定义一个模块 |
| 公共项 | pub fn 函数名() {} | 定义一个公共函数 |
| 模块嵌套 | mod 模块名 { mod 子模块 { 代码 } } | 定义嵌套模块 |
| 使用模块 | use 模块名::项; | 简化模块的使用 |
| Result类型 | fn 函数() -> Result<T, E> { 代码 } | 定义可能失败的函数 |
| 错误传播 | let 结果 = 可能失败的操作()?; | 自动传播错误 |
| 错误处理 | match 结果 { Ok(值) => 处理成功, Err(错误) => 处理错误 } | 处理Result类型 |
常见错误提醒
- 访问控制:忘记使用
pub关键字,导致外部无法访问模块项 - 模块路径:模块路径错误,导致无法找到模块项
- 错误处理:忘记处理Result类型,导致编译错误
- ?操作符:在非Result返回类型的函数中使用?操作符
- panic!:过度使用panic!,应该使用Result类型处理可恢复的错误
5. 课后作业
巩固练习题
- 编写一个程序,创建一个"数学工具"模块,包含加减乘除函数
- 编写一个程序,创建一个"字符串工具"模块,包含字符串处理函数
- 编写一个程序,使用Result类型处理文件读写错误
创意编程题
- 发挥你的创意,创建一个完整的模块系统,包含多个子模块
- 编写一个程序,实现一个简单的错误处理系统,处理各种可能的错误
下篇预习提示
下一篇我们将学习"综合实战——制作我的小游戏",这是一个综合运用所学知识的项目。预习一下:
- 如何选择一个适合的项目?
- 如何设计项目的结构?
- 如何实现项目的功能?
- 如何测试和调试项目?
恭喜你完成了第十二篇教程!你已经掌握了模块系统和错误处理,学会了如何组织代码和处理问题。继续加油,下一篇我们将进行综合实战,制作一个完整的小游戏!
学习交流
如果你在学习过程中遇到任何问题,欢迎联系我:
- ** 视频号,“时空系”
- 邮箱: wbtm2718@qq.com
- 其他提示:
- 本人正在开发纯中文编程语言和编译器,有兴趣的可以交流
- 本人正在写一本基于中国母语思维习惯的数学教材,可以让你一年内学完从初中到研究生的数学,而且可以让你快速掌握人工智能的关键数学知识,有兴趣的可以交流
祝你学习愉快!
更多推荐
所有评论(0)