通过 Rspack 源码学 Rust(一):`mod`、`pub` 与 `use crate`/`use super`

文章来源声明: 原文作者:Amos_zz; 来源站点:掘金; 原文链接:https://juejin.cn/post/7700499904883408946; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

适合正在读 Rust 大型项目源码的开发者,用 Rspack 实例把模块边界与路径解析讲得具体,能帮助建立从入口到实现的可靠阅读路径。

> Rspack 的 Rust 代码被拆成很多 crate 和模块。读懂 `mod`、可见性和路径解析,才能从一个入口跟到插件、运行时模块和工具函数,而不会被文件目录误导。本篇只讲两组紧密相关的基础:模块边界与路径导入。
  1. mod 是编译单元,不只是目录

Rust 用 mod 把代码挂到模块树上。文件名通常只是模块的默认来源:runtime_module/mod.rs 声明子模块,子文件中的内容才会进入同一棵模块树。

Rspack 的 crates/rspack_plugin_runtime/src/runtime_module/mod.rs 会重新导出多个运行时模块;module_chunk_loading.rs 再通过 use super::utils::get_output_dir 访问同级父模块下的工具。这种组织让每个运行时模块保持独立,同时由 mod.rs 提供统一出口。

最小例子:

mod config {
    pub const NAME: &str = "rspack";

    pub mod output {
        pub fn describe() -> String {
            format!("{} output", super::super::NAME)
        }
    }
}

fn main() {
    println!("{}", config::output::describe());
}

这里 config 是根模块的子模块,output 是 config 的子模块。super::super::NAME 先回到 config,再回到根模块;NAME 必须是 pub,否则 output 不能读取它。

  1. pub 控制边界,默认是私有

模块项默认只对当前模块及其子模块可见。pub 不是“整个工程都能访问”的开关,而是把该项暴露给父模块之外的路径;跨 crate 使用时还需要 crate 本身在依赖清单中声明,并且路径上的每一级模块都可见。

Rspack 的 ModuleChunkLoadingPlugin 声明为 pub struct,并在 lib.rs 中通过 pub use module_chunk_loading::ModuleChunkLoadingPlugin; 重新导出。这样其他 crate 不必依赖内部文件路径,可以从 rspack_plugin_runtime::ModuleChunkLoadingPlugin 访问它。这个 re-export 是 API 边界:内部文件可以调整,公开入口保持稳定。

下面的错误示例会被编译器拒绝:

mod internal {
    fn secret() -> &'static str {
        "private"
    }
}

fn main() {
    println!("{}", internal::secret());
}

错误原因不是函数没有实现,而是 secret 对根模块外部路径不可见。将它改为 pub fn secret() 后,路径才合法。可见性错误通常意味着 API 边界没有设计好,而不是应该盲目给所有项目加 pub。

  1. use crate 与 use super 的路径语义

Rspack 运行时模块中有两类典型导入:

use super::utils::get_output_dir;
use crate::{LinkPrefetchData, LinkPreloadData, RuntimePlugin};

  • crate:: 从当前 crate 的根模块开始解析。它适合引用跨多个子模块共享的类型、trait 或公共工具。
  • super:: 从当前模块的父模块开始解析。它适合同一模块树内部的邻近实现,表达“我依赖父模块下的兄弟项”。

这两个前缀都是绝对的模块树路径,不依赖调用者从哪里进入当前函数。相比大量 ../../ 文件路径,它们能直接表达 Rust 的命名空间关系。

如果模块移动了位置,super:: 可能需要调整;如果希望内部实现移动而调用方不变,可以在上层 mod.rs 用 pub use 建立稳定入口,再让外部代码使用 crate::runtime_module::ModuleChunkLoadingRuntimeModule 这样的路径。

  1. 最小可运行示例与验证

下面的示例同时使用 crate:: 和 super:::

mod model {
    pub struct Chunk {
        pub id: u32,
    }
}

mod runtime {
    use crate::model::Chunk;

    pub mod formatter {
        use super::Chunk;

        pub fn render(chunk: &Chunk) -> String {
            format!("chunk:{}", chunk.id)
        }
    }
}

fn main() {
    let chunk = model::Chunk { id: 7 };
    println!("{}", runtime::formatter::render(&chunk));
}

我用 rustc 编译并运行该示例,输出为:

chunk:7

runtime 先用 crate::model::Chunk 把类型导入自己的作用域;嵌套的 formatter 再用 super::Chunk 继承父模块已经导入的名称。注意,use 只创建当前模块的名称别名,不会改变类型的所有权,也不会把名称自动导出给子模块;需要对外暴露时仍要写 pub use。

  1. 与 Rspack 工程的对应关系

读取 ModuleChunkLoadingPlugin 时,可以按以下顺序定位:

  1. lib.rs 通过 pub use 暴露插件,并在 Import 分支构造它。
  2. module_chunk_loading.rs 通过 use rspack_core::{...} 引入跨 crate API。
  3. 同一 crate 的运行时模块通过 use crate::runtime_module::{...} 访问公共模块出口。
  4. 运行时模块文件通过 use super::utils::... 使用父模块树中的工具。

这个路径层次把公开 API、crate 内共享能力和相邻实现区分开。读源码时先看模块声明与 re-export,再追具体函数,通常比从文件名猜调用关系更可靠。

  1. 常见误区与适用边界

  1. 文件夹存在不代表模块自动存在;必须由 mod 声明把它纳入模块树。
  2. use 是导入名称,不是可见性提升;私有项被导入后仍然私有。
  3. pub 只解决 Rust 模块可见性,不会替代 Cargo 的 crate 依赖声明。
  4. 内部实现优先保持私有,通过 pub use 暴露少量稳定入口,能降低重构成本。