在 Rust 中,mod(模块)是组织代码的 “乐高积木”—— 它能帮你把零散的函数、结构体、枚举等 “零件” 按功能分类,拼出结构清晰的程序。如果你写过其他语言,可以把模块理解为 “文件夹”:就像用文件夹整理文件一样,模块帮你整理代码,避免杂乱无章。

而这一切的 “整理逻辑”,都围绕一个核心 ——crate 根。今天我们就从 “crate 根” 说起,一步步搞懂模块的使用。


一、先搞懂:什么是 crate 根?

在 Rust 中,“crate” 可以理解为 “项目”(比如一个可执行程序或一个库)。每个 crate 都有一个 “起点”,这个起点就是 crate 根

  • 如果你写的是一个库(比如供其他项目引用的工具包),crate 根是 src/lib.rs
  • 如果你写的是一个可执行程序(比如命令行工具、应用),crate 根是 src/main.rs

简单说,crate 根就是项目的 “主文件”,所有模块最终都像树枝一样,从这个 “主根” 延伸出去,形成一棵 “模块树”。


二、模块的核心作用

为什么需要模块?想象一下,如果你写一个电商系统,里面有用户登录、商品管理、订单处理等功能。如果把所有代码堆在一个文件里,找个函数都要翻半天。而模块能帮你:

  1. 分类功能:把 “用户相关” 的代码放进 user 模块,“商品相关” 的放进 product 模块;
  2. 控制访问:比如 user 模块里的密码加密函数,不想让外部随便调用,就可以设为 “私有”;
  3. 避免重名user 模块里可以有 get_info 函数,product 模块里也可以有同名函数,互不冲突。

三、定义模块的三种方式(从 crate 根开始)

模块的定义方式和文件结构紧密相关,根据代码多少,有三种常用形式,且都以 crate 根为起点构建层级。

1. 内部模块:简单功能,直接写在 crate 根里

如果功能很简单(比如几个工具函数),可以直接在 crate 根(如 lib.rs)里用 mod 模块名 { ... } 定义模块。这种模块就像 “主文件里的小文件夹”,直接嵌套在 crate 根下。

示例src/lib.rs):

// 这是 crate 根(lib.rs)

// 定义一个名为 math 的内部模块(直接嵌套在 crate 根下)
mod math {
    // 模块内的函数(默认“私有”,只能在 math 模块里用)
    fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    // 用 pub 修饰的函数是“公开”的,模块外也能调用
    pub fn multiply(a: i32, b: i32) -> i32 {
        a * b
    }

    // 模块里还能嵌套子模块(就像文件夹里套子文件夹)
    pub mod advanced {
        // 这个函数是 advanced 子模块的公开函数
        pub fn square(n: i32) -> i32 {
            n * n
        }
    }
}

// 在 crate 根里使用 math 模块的功能
pub fn use_math() {
    // 错误:add 是 math 模块的私有函数,外面用不了
    // let sum = math::add(2, 3);

    // 正确:multiply 是公开函数,能直接用
    let product = math::multiply(2, 3);
    println!("2 × 3 = {}", product); // 输出:2 × 3 = 6

    // 访问子模块 advanced 的公开函数(路径:math::advanced::square)
    let square = math::advanced::square(4);
    println!("4 的平方 = {}", square); // 输出:4 的平方 = 16
}

2. 文件模块:代码较多?单独放一个 .rs 文件

如果模块里的代码太多(比如超过 100 行),塞在 crate 根里会显得臃肿。这时可以把模块代码拆到单独的 .rs 文件中,再在 crate 根里 “声明” 这个模块。

步骤演示
假设我们要做一个 “字符串工具” 模块(utils),代码较多,单独放文件。

(1)创建模块文件:在 src 目录下新建 utils.rs,写模块内容(不用重复写 mod utils):

// src/utils.rs(模块内容)
// 公开函数:格式化字符串
pub fn format_str(s: &str) -> String {
    format!("【{}】", s) // 给字符串加个括号,比如 "test" → "【test】"
}

// 私有函数:模块内部用的辅助函数(外面看不到)
fn trim_whitespace(s: &str) -> &str {
    s.trim()
}

(2)在 crate 根中声明模块:在 src/lib.rs 里用 mod utils; 告诉 Rust:“有个叫 utils 的模块,代码在 utils.rs 里”。这样 utils 就成了 crate 根的直接子模块。

// src/lib.rs(crate 根)
// 声明 utils 模块(关联 src/utils.rs)
mod utils;

// 使用 utils 模块的公开函数
pub fn use_utils() {
    let result = utils::format_str("hello");
    println!("格式化后:{}", result); // 输出:格式化后:【hello】
}

3. 目录模块:复杂模块?用文件夹组织多个文件

如果一个模块包含多个子功能(比如 “计算模块” 里有 “基础计算” 和 “高级计算”),可以用文件夹来组织。这种方式就像 “用一个大文件夹装多个相关的小文件”。

目录结构示例
我们要做一个 calc 模块,包含 basic(基础计算)和 advanced(高级计算)两个子模块,结构如下:

src/
├── lib.rs          # crate 根
└── calc/           # 大模块:calc(文件夹)
    ├── basic.rs    # 子模块:calc::basic(基础计算)
    ├── advanced.rs # 子模块:calc::advanced(高级计算)
    └── mod.rs      # calc 模块的“入口文件”(声明子模块)

步骤演示

(1)写子模块代码

  • src/calc/basic.rs(基础计算,比如加法):
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
  • src/calc/advanced.rs(高级计算,比如乘方):
pub fn power(n: i32, exp: u32) -> i32 {
    (0..exp).fold(1, |acc, _| acc * n) // 计算 n 的 exp 次方
}

(2)写模块入口文件(mod.rs)
在 calc 文件夹里新建 mod.rs,声明子模块(告诉 Rust 这个文件夹里有哪些子模块):

// src/calc/mod.rs
// 声明子模块(用 pub 修饰,允许外部访问)
pub mod basic;
pub mod advanced;

//  calc 模块自身的函数(比如版本信息)
pub fn version() -> &'static str {
    "v1.0.0"
}

(3)在 crate 根中声明 calc 模块
在 src/lib.rs 里用 mod calc; 关联 calc 文件夹,使其成为 crate 根的子模块:

// src/lib.rs(crate 根)
// 声明 calc 模块(关联 calc/ 文件夹)
mod calc;

// 使用 calc 模块及其子模块
pub fn use_calc() {
    // 调用 calc::basic::add(路径:crate 根 → calc → basic → add)
    let sum = calc::basic::add(2, 3);
    println!("2 + 3 = {}", sum); // 输出:2 + 3 = 5

    // 调用 calc::advanced::power
    let pow = calc::advanced::power(2, 3); // 2 的 3 次方
    println!("2^3 = {}", pow); // 输出:2^3 = 8

    // 调用 calc 模块自身的函数
    println!("calc 模块版本:{}", calc::version()); // 输出:calc 模块版本:v1.0.0
}

小技巧:Rust 2018 之后,可以省略 mod.rs。直接在 crate 根里嵌套声明子模块即可:

// src/lib.rs
// 直接声明 calc 模块,并嵌套子模块(自动关联 calc/basic.rs 和 calc/advanced.rs)
pub mod calc {
    pub mod basic;
    pub mod advanced;

    pub fn version() -> &'static str { "v1.0.0" }
}

四、控制可见性:谁能访问模块里的内容?

模块里的内容(函数、结构体等)默认是 “私有” 的(只有自己能访问)。如果想让外部访问,需要用 pub 关键字 “公开”。就像文件夹的权限:有的文件能被所有人看,有的只能自己看。

常见的可见性修饰符:

修饰符 含义 例子场景
无(默认) 仅当前模块内可访问 模块内部的辅助函数
pub 所有模块都能访问(只要路径正确) 对外提供的 API 函数
pub(crate) 仅当前 crate 内可访问(对外不可见) 项目内部共用的工具函数
pub(super) 仅父模块可访问 父模块需要调用的子模块内部函数

示例

// 公开模块(外面能看到这个模块)
pub mod public_mod {
    // 公开函数(所有模块都能调用)
    pub fn public_func() {
        println!("我是公开函数");
    }

    // 私有函数(只有 public_mod 内部能调用)
    fn private_func() {
        println!("我是私有函数");
    }

    // 仅当前 crate 内可访问的函数
    pub(crate) fn crate_only_func() {
        println!("只有本项目能调用我");
    }

    // 子模块
    pub mod inner_mod {
        // 仅父模块(public_mod)可访问
        pub(super) fn parent_only_func() {
            println!("只有父模块能调用我");
        }
    }
}

// 在其他地方使用
pub fn test_visibility() {
    public_mod::public_func(); // 可以调用(pub)
    public_mod::crate_only_func(); // 可以调用(pub(crate),因为在同一个项目)
    
    // 错误:private_func 是私有函数
    // public_mod::private_func();
    
    // 错误:parent_only_func 仅允许父模块(public_mod)调用
    // public_mod::inner_mod::parent_only_func();
}

五、引用模块:怎么找到要调用的函数?

模块里的函数就像 “文件夹里的文件”,要调用它,得告诉 Rust 它在 “哪个文件夹里”(路径)。有两种常用路径:

1. 绝对路径:从 crate 根出发(推荐)

绝对路径以 crate:: 开头,就像从 “项目根目录” 出发找文件(比如 crate::calc::basic::add)。优点是路径固定,不管在哪个模块里调用,都能准确找到。

示例

// src/lib.rs(crate 根)
mod calc {
    pub mod basic {
        pub fn add(a: i32, b: i32) -> i32 { a + b }
    }
}

// 另一个模块(other_mod)
mod other_mod {
    pub fn use_add() {
        // 绝对路径:从 crate 根出发 → calc → basic → add
        let sum = crate::calc::basic::add(2, 3);
        println!("sum: {}", sum);
    }
}

2. 相对路径:从当前模块出发

相对路径以 self::(当前模块)或 super::(父模块)开头,就像 “从当前文件夹出发找文件”。适合在模块内部或相邻模块间调用。

示例

mod parent {
    pub mod child {
        pub fn func() {
            println!("我是 child 模块的函数");
        }
    }

    pub fn call_child() {
        // 相对路径:从当前模块(parent)出发 → child → func
        child::func();
    }
}

pub fn call_parent() {
    // 相对路径:从当前模块(crate 根)出发 → parent → call_child
    parent::call_child();
}

3. 用 use 简化路径:少写重复代码

如果一个函数要调用很多次,每次都写长路径很麻烦。可以用 use 关键字把路径 “导入” 当前模块,之后直接用函数名调用。

示例

// 导入绝对路径:crate::calc::basic::add
use crate::calc::basic::add;

pub fn demo() {
    // 直接用 add,不用写完整路径
    let sum1 = add(2, 3);
    let sum2 = add(4, 5);
    println!("sum1: {}, sum2: {}", sum1, sum2);
}

// 也可以导入整个模块
use crate::calc::advanced;

pub fn demo2() {
    // 用“模块名::函数名”调用
    let pow = advanced::power(2, 3);
    println!("2^3: {}", pow);
}

六、常见问题与最佳实践

  1. “模块找不到” 怎么办?
    检查:① 文件名 / 目录名是否和模块名一致(比如 mod utils; 对应 utils.rs);② 是否在父模块中声明了子模块(比如 calc 模块是否在 lib.rs 中用 mod calc; 声明);③ 路径是否正确(绝对路径用 crate:: 开头,相对路径是否漏了 super::)。

  2. 模块层级不要太深
    建议模块层级不超过 3-4 层(比如 a::b::c::d 就有点深了),否则路径太长,不好维护。

  3. crate 根只做 “入口”
    lib.rs 或 main.rs 里尽量少写具体逻辑,只用来声明模块(比如 mod user; mod product;),让代码结构更清晰。

  4. 公开 API 要 “精挑细选”
    不是所有函数都要 pub,只公开必须让外部使用的功能,其他逻辑用私有修饰符隐藏,减少误用风险。


总结

模块是 Rust 代码组织的核心,记住三个关键点:

  • crate 根lib.rs 或 main.rs)是所有模块的起点,就像项目的 “主目录”;
  • 用 mod 定义模块,可嵌套在文件内、单独放 .rs 文件或用文件夹组织;
  • 用 pub 控制可见性,用绝对路径(crate::xxx)或相对路径引用模块内容,配合 use 简化调用。

掌握模块系统,你就能写出结构清晰、易于维护的 Rust 代码了!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐