Rust语言的模块源码组织和单元测试详解

 更新时间:2026年08月27日 10:15:09   作者:麻瓜老宋  
本文以一个可运行的demo工程为例,循序渐进地演示Rust的模块组织(lib.rs、独立文件模块、目录模块)与单元测试写法,文中代码均可直接复制运行

本文以一个可运行的 demo 工程为例,循序渐进地演示 Rust 的模块组织(lib.rs、独立文件模块、目录模块)与单元测试写法,文中代码均可直接复制运行。环境:Windows + Rust(edition 2024)。

  • 软件开发往往要编辑大量的源码,如何组织更方便管理是刚开始学习Rust语言就应该考虑的。
  • Cargo是Rust语言的工程工具,涉及到工程的创建、编译、运行、测试、文档等方方面面,是初学者要熟练掌握的。

创建工程

  • 用cargo的new命令:
cargo new demo
  • 此时demo目录组织如下:
demo
  |---- Cargo.lock
  |---- Cargo.toml
  |____ src
         |____ main.rs
  • main.rs是Rust语言的Hello World,内容如下:
fn main() {
    println!("Hello, world!");
}
  • 编译运行输出如下:
$ cargo run
   Compiling demo v0.1.0 (path\to\demo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 10.87s
     Running `target\debug\demo.exe`
Hello, world!

如何组织更多的源代码文件呢?对,用模块!

  • 此时的可执行文件名为demo,默认模块名也为demo!
  • 二进制 crate 的根是 main.rs;库 crate 的根是 lib.rs,cargo 不会自动创建 lib.rs,需要我们手动添加!
  • 创建lib.rs,内容如下:
// 默认模块名为demo
// 定义一个简单的加法函数,声明为pub,供外部调用,否则不能为外部调用
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
  • 修改main.rs中的主函数,内容如下:
//主函数,程序运行入口
fn main() {
    let a = 88;
    let b = 99;
    let x = demo::add(a, b); //直接调用模块中的函数
    println!("{a}+{b}={x}");
}
  • 编译运行,输出如下:
$ cargo run
   Compiling demo v0.1.0 (path\to\demo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.78s
     Running `target\debug\demo.exe`
88+99=187

更多的自定义模块

  • 如何自定义一个模块呢?上面只是一种途径,还有两种途径。

1、直接在源码中添加,例如直接在main.rs中添加如下:

//主函数,程序运行入口
fn main() {
    let a = 88;
    let b = 99;
    let x = foo::add(a, b); //直接调用模块中的函数
    println!("{a}+{b}={x}");
}
// 定义模块,名字为foo
mod foo {
    //定义一个简单的加法函数,声明为pub,供外部调用,否则不能为外部调用
    pub fn add(a:i32, b:i32) -> i32 {
        a+b
    }
}
  • 说明:lib.rs 里的代码不注释也不会冲突(可执行 crate 与库 crate 相互独立),可保留!
  • 运行效果如下:
$ cargo run
   Compiling demo v0.1.0 (path\to\demo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.41s
     Running `target\debug\demo.exe`
88+99=187

2、在新的源码foo.rs中自定义模块,在lib.rs中声明,在main.rs中调用

  • lib.rs内容如下:
// 默认模块名为demo
// 添加模块foo,指向foo.rs,调用时语法为demo::foo::add
pub mod foo;
  • foo.rs内容如下:
// 模块foo,是主模块demo的子模块,调用语法为demo::foo::add
// 定义一个简单的加法函数,声明为pub,供外部调用,否则不能为外部调用
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
  • main.rs内容如下:
//主函数,程序运行入口
fn main() {
    let a = 88;
    let b = 99;
    let x = demo::foo::add(a, b); //直接调用模块中的函数
    println!("{a}+{b}={x}");
}
  • 编译运行,效果同上!

增加单元测试模块tests

  • 在foo.rs中添加tests模块,内容如下:
// 模块foo,是主模块demo的子模块,调用语法为demo::foo::add
// 定义一个简单的加法函数,声明为pub,供外部调用,否则不能为外部调用
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
// 单元测试模块,不会编译到主程序中去!对应cargo test命令!
#[cfg(test)]
mod tests {
    // 引用上面定义的所有函数
    use super::*;
    // 单元测试函数,测试时自动执行
    #[test]
    fn add_works() {
        let result = add(90, 100);
        assert_eq!(result, 190);
    }
}
  • 在模块定义前加#[cfg(test)]属性,标明为单元测试模块!
  • 在测试函数前加#[test]属性,标明为测试函数!
  • 单元测试需要执行cargo test命令!
  • cargo test输出如下:
$ cargo test
   Compiling demo v0.1.0 (path\to\demo)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.55s
     Running unittests src\lib.rs (target\debug\deps\demo-<hash>.exe)
running 1 test
test foo::tests::add_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
     Running unittests src\main.rs (target\debug\deps\demo-<hash>.exe)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
   Doc-tests demo
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
  • 能看到测试函数foo::tests::add_works测试运行通过!
  • 输出中的demo-.exe,hash为编译指纹,因环境而异!
  • 注意,在Rust语言中单元测试代码和最终编译的可执行文件互相不干扰!

根源所在

  • Rust语言在搜索模块foo时,先看当前源码中是否内联定义了mod foo { ... },再找源码名为foo.rs的文件(需要在lib.rs中声明pub mod foo;),第三找目录foo下的mod.rs文件。注意foo.rs与foo/mod.rs不能同时存在,否则编译器会报错!
  • 创建一个bar目录,在bar目录下创建mod.rs,内容如下:
// 模块bar
// 定义简单的hi函数
pub fn hi(name:&str) {
    println!("Hi, {name}");
}
  • 在lib.rs中声明,lib.rs内容如下:
// 默认模块名为demo
// 添加模块foo,指向foo.rs,调用时语法为demo::foo::add
pub mod foo;
// 添加模块bar,指向bar/mod.rs,调用时语法为demo::bar::hi
pub mod bar;
  • 在main.rs中调用,内容如下:
//主函数,程序运行入口
fn main() {
    demo::bar::hi("Tomson");
}
  • 编译运行,输出如下:
$ cargo run
   Compiling demo v0.1.0 (path\to\demo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.25s
     Running `target\debug\demo.exe`
Hi, Tomson
  • 如此看来,组织模块代码和单元测试的坑就算是跳过去了!!!

小结

  • 三种模块组织方式:
    • 在当前源码中内联定义:mod foo { ... }
    • 独立文件模块:foo.rs + 在 lib.rs 中声明 pub mod foo;
    • 目录模块:foo/mod.rs + 在 lib.rs 中声明 pub mod foo;
  • 模块查找顺序:内联定义 → foo.rs → foo/mod.rs;foo.rs 与 foo/mod.rs 不能同时存在!
  • 单元测试:#[cfg(test)] + #[test],用 cargo test 运行,测试代码不编译进最终的可执行文件!
  • 调用路径随组织方式变化:demo::add → foo::add → demo::foo::add → demo::bar::hi

到此这篇关于Rust语言的模块源码组织和单元测试详解的文章就介绍到这了,更多相关Rust模块源码组织和单元测试内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Rust中引用的具体使用

    Rust中引用的具体使用

    在Rust语言中,引用机制是其所有权系统的重要组成部分,ust提供了两种类型的引用,不可变引用和可变引用,本文就来详细的介绍一下这两种的用法,感兴趣的可以了解一下
    2024-03-03
  • Rust之模式与模式匹配的实现

    Rust之模式与模式匹配的实现

    Rust中的模式匹配功能强大且灵活,它极大地提高了代码的表达力和可读性,本文主要介绍了Rust之模式与模式匹配,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • 2022最新Rust变量与数据类型讲解

    2022最新Rust变量与数据类型讲解

    rust 是强类型语言所有变量、常量都必须有明确的数据类型,这篇文章主要介绍了Rust变量与数据类型,需要的朋友可以参考下
    2022-11-11
  • 一文学会Rust语言如何操作JSON

    一文学会Rust语言如何操作JSON

    JSON在Web开发中被广泛应用于数据交换,本文主要介绍了Rust语言操作JSON,包括序列化、反序列化、JSON创建等多个方面,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • C和Java没那么香了,Serverless时代Rust即将称王?

    C和Java没那么香了,Serverless时代Rust即将称王?

    Serverless Computing,即”无服务器计算”,其实这一概念在刚刚提出的时候并没有获得太多的关注,直到2014年AWS Lambda这一里程碑式的产品出现。Serverless算是正式走进了云计算的舞台
    2021-06-06
  • Rust安装的图文教程

    Rust安装的图文教程

    本文主要介绍了Rust安装的图文教程,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-06-06
  • Rust之Substrate框架中的pallet详解

    Rust之Substrate框架中的pallet详解

    Pallet是Substrate区块链的基础模块,它定义了区块链的业务逻辑和状态转换规则。开发人员可以使用现有的Pallet来快速构建区块链,也可以开发自定义的Pallet来实现特定的功能,感兴趣的小伙伴可以参考阅读下
    2023-05-05
  • 利用rust编一个静态博客工具

    利用rust编一个静态博客工具

    这篇文章主要为大家详细介绍了如何利用rust编一个静态博客工具,这个静态博客的工具主要是把md文档转为html静态网站/博客,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-12-12
  • Rust之Rhai脚本编程的示例

    Rust之Rhai脚本编程的示例

    文章介绍了如何在Rust中使用Rhai库执行脚本,包括添加依赖、创建引擎、定义脚本、执行脚本并处理结果,示例展示了如何定义一个简单的函数并调用它,输出问候信息
    2025-02-02
  • Rust动态调用字符串定义的Rhai函数方式

    Rust动态调用字符串定义的Rhai函数方式

    Rust中使用Rhai动态调用字符串定义的函数,通过eval_expression_with_scope实现,但参数传递和函数名处理有局限性,使用FnCall功能更健壮,但更复杂,总结提供了更通用的方法,但需要处理更多错误情况
    2025-02-02

最新评论