3. 模块系统篇¶
对应章节:第7章(package / crate / module)。
本篇掌握项目的组织方式:库与二进制分离、模块拆分。
项目 7:几何计算器¶
目标:拆分库与二进制,用模块组织圆形、矩形、三角形计算。
创建项目¶
说明:本项目 bin 与 lib 同名(都叫
geometry),Cargo 会自动把src/main.rs作为二进制 crate、src/lib.rs作为库 crate,并在二进制中识别库。lib 与 bin 同名会自动发现,无需额外配置。
功能要求¶
src/lib.rs:声明模块src/circle.rs、src/rect.rs、src/triangle.rs:各形状实现src/main.rs:调用库函数并打印结果
项目结构:
geometry/
├── Cargo.toml
└── src/
├── lib.rs ← 库 crate 根,声明各模块
├── main.rs ← 二进制 crate,使用库
├── circle.rs
├── rect.rs
└── triangle.rs
知识点¶
| 知识点 | 说明 |
|---|---|
| package / crate | 项目与编译单元 |
mod 声明 |
模块系统 |
pub |
公开可见性(默认私有) |
| lib + bin 共存 | 同名自动互认 |
use geometry::circle; |
路径导入 |
代码¶
src/lib.rs:
// lib.rs 是库 crate 的根
// mod 声明告诉编译器"有这些模块",会自动查找同名 .rs 文件
pub mod circle;
pub mod rect;
pub mod triangle;
src/circle.rs:
// std::f64::consts::PI:标准库圆周率常量,别手写 3.14
pub fn area(radius: f64) -> f64 {
std::f64::consts::PI * radius * radius
}
pub fn circumference(radius: f64) -> f64 {
2.0 * std::f64::consts::PI * radius
}
src/rect.rs:
pub fn area(width: f64, height: f64) -> f64 {
width * height
}
pub fn perimeter(width: f64, height: f64) -> f64 {
2.0 * (width + height)
}
src/triangle.rs:
src/main.rs:
// 从库 crate(名 geometry)导入各模块
// 库和二进制同名时,二进制会自动把库作为依赖
use geometry::circle;
use geometry::rect;
use geometry::triangle;
fn main() {
let r = 3.0;
// 模块名::函数名 调用,路径清晰
println!("圆:面积 {:.2},周长 {:.2}",
circle::area(r), circle::circumference(r));
let (w, h) = (4.0, 5.0);
println!("矩形:面积 {:.2},周长 {:.2}",
rect::area(w, h), rect::perimeter(w, h));
println!("三角形:面积 {:.2}", triangle::area(6.0, 4.0));
}
运行¶
注意事项¶
- lib 与 bin 同名会自动发现:
src/main.rs是 bin,src/lib.rs是 lib,两者名字相同(都叫geometry),Cargo 自动让二进制识别库 crate。 - 模块默认私有:所有跨模块使用的东西都要
pub,否则编译报错。 use geometry::circle;导入模块:然后circle::area(...)调用,路径清晰。std::f64::consts::PI:标准库自带圆周率常量,别手写 3.14。
进阶:目录模块与 pub use 重导出¶
- 目录模块:三角形代码拆成目录
src/triangle/——mod.rs是模块入口,helper.rs是它的子模块 pub use重导出:lib.rs 把深层路径"搬"到 crate 根,外部直接用geometry::area_of_circle(...)super/crate相对路径:子模块里向上引用的两种写法实战- 海伦公式:已知三边求面积,顺便练
.sqrt() - 子命令 main:
cargo run -- circle 5按命令分发
src/lib.rs(circle.rs / rect.rs 不变):
pub mod circle;
pub mod rect;
pub mod triangle;
// pub use 重导出:把内部路径映射成对外更短的路径
pub use circle::area as area_of_circle;
pub use circle::circumference as circumference_of_circle;
pub use rect::area as area_of_rectangle;
pub use rect::perimeter as perimeter_of_rectangle;
pub use triangle::area as area_of_triangle;
pub use triangle::perimeter as perimeter_of_triangle;
src/triangle/mod.rs(目录模块入口,不再是 triangle.rs):
pub mod helper;
pub fn area(a: f64, b: f64, c: f64) -> f64 {
// 海伦公式:s = 半周长,面积 = sqrt(s(s-a)(s-b)(s-c))
let s = 0.5 * (a + b + c);
(s * (s - a) * (s - b) * (s - c)).sqrt()
}
pub fn perimeter(a: f64, b: f64, c: f64) -> f64 {
a + b + c
}
// super = 父模块,相对路径向上走一层
pub fn uses_super() -> f64 {
super::area_of_circle(1.0)
}
src/triangle/helper.rs:
pub fn compare_with_circle(radius: f64) -> String {
// crate:: 从 crate 根出发找 circle 模块
let circle_area = crate::circle::area(radius);
// super:: 从当前模块(triangle.helper)上跳到 triangle
let tri_area = super::area(3.0, 4.0, 5.0);
format!(
"Circle area: {:.2}, Triangle area: {:.2}",
circle_area, tri_area
)
}
src/main.rs:
use geometry;
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let command = args[1].as_str();
match command {
"circle" => {
let radius: f64 = args[2].parse::<f64>().expect("Invalid radius");
let area = geometry::area_of_circle(radius); // 重导出的短路径
let circumference = geometry::circumference_of_circle(radius);
println!("Area: {}", area);
println!("Circumference: {}", circumference);
}
"rect" => {
let width: f64 = args[2].parse::<f64>().expect("Invalid width");
let height: f64 = args[3].parse::<f64>().expect("Invalid height");
println!("Area: {}", geometry::area_of_rectangle(width, height));
println!("Perimeter: {}", geometry::perimeter_of_rectangle(width, height));
}
"triangle" => {
let (a, b, c): (f64, f64, f64) = (
args[2].parse().unwrap(),
args[3].parse().unwrap(),
args[4].parse().unwrap(),
);
println!("Area: {}", geometry::area_of_triangle(a, b, c));
println!("Perimeter: {}", geometry::perimeter_of_triangle(a, b, c));
}
"super_test" => {
println!("{}", geometry::triangle::uses_super());
}
"path_test" => {
println!("{}", geometry::triangle::helper::compare_with_circle(5.0));
}
_ => {
println!("Unknown command: {}", command);
}
}
}
运行¶
cargo run -- circle 5
cargo run -- rect 3 4
cargo run -- triangle 3 4 5 # Area: 6
cargo run -- super_test # π ≈ 3.141592653589793
cargo run -- path_test # Circle area: 78.54, Triangle area: 6.00
要点¶
- 目录模块规则:
src/triangle/mod.rs让triangle成为带子模块的目录;也可写成src/triangle.rs+src/triangle/helper.rs的现代风格,两者等价。 pub use是门面:模块内部保持清晰层次,对外暴露扁平 API——标准库和大型 crate 都这么做(如rand::rng其实来自深层路径)。super::vscrate:::super相对上跳一层,适合就近引用父模块;crate绝对定位根,适合跨层级引用。- 改进练习:main.rs 各分支参数解析重复较多,学完第9章后可把解析封装成返回
Result的辅助函数。