跳转至

3. 模块系统篇

对应章节:第7章(package / crate / module)。
本篇掌握项目的组织方式:库与二进制分离、模块拆分。


项目 7:几何计算器

目标:拆分库与二进制,用模块组织圆形、矩形、三角形计算。

创建项目

cargo new geometry
cd geometry

说明:本项目 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:

pub fn area(base: f64, height: f64) -> f64 {
    base * height / 2.0
}

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));
}

运行

cargo run
# 圆:面积 28.27,周长 18.85
# 矩形:面积 20.00,周长 18.00
# 三角形:面积 12.00

注意事项

  1. lib 与 bin 同名会自动发现:src/main.rs 是 bin,src/lib.rs 是 lib,两者名字相同(都叫 geometry),Cargo 自动让二进制识别库 crate。
  2. 模块默认私有:所有跨模块使用的东西都要 pub,否则编译报错。
  3. use geometry::circle; 导入模块:然后 circle::area(...) 调用,路径清晰。
  4. 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

要点

  1. 目录模块规则:src/triangle/mod.rs 让 triangle 成为带子模块的目录;也可写成 src/triangle.rs + src/triangle/helper.rs 的现代风格,两者等价。
  2. pub use 是门面:模块内部保持清晰层次,对外暴露扁平 API——标准库和大型 crate 都这么做(如 rand::rng 其实来自深层路径)。
  3. super:: vs crate:::super 相对上跳一层,适合就近引用父模块;crate 绝对定位根,适合跨层级引用。
  4. 改进练习:main.rs 各分支参数解析重复较多,学完第9章后可把解析封装成返回 Result 的辅助函数。

评论