本文以一个可运行的 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模块源码组织和单元测试内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论