Rust编程从入门到实战 Rust 内置了一套单元测试框架,可以显著提升代 码可靠性的工程管理手段。
更复杂的数据类型
对于交互接口中的简单类型,我们直接使用标准库中定义好的std::os::raw 里面的 类型就够了。而更复杂的类型就需要我们手动封装了。比如结构体就需要用#[repr©]修饰,以保证这个结构体在Rust和 C 双方的内存布局是一致的。
如果我们需要做的是跟常见的操作系统交互,许多常用的数据结构都已经有人封装好了, 可以在crates.io 找libc 库直接使用。接下来我们用一个示例演示一下在接口中包含结构体该怎 样做。这个示例同时也使用了cargo来管理Rust项目,且使用动态链接库的方式执行。
Rust 项 目Cargo.toml 如 下 所 示 :
[package] name = "log"version = "0.1.0"
authors = ["F001"][dependencies] libc = "0.2" [lib]
src / lib.rs文件内容如下所示:# ! [crate_type = "cdylib"] extern crate libc;
use libc: :{
c_int,
c_char
};
use std: :ffi: :CStr;
//this struct is used as an public interface #[repr(C)]
# [no_mangle] pub struct RustLogMessage {
id: c_int,
msg: *const c_char
}# [no_mangle] pub extern "C"fn rust_log(msg: RustLogMessage) {
let s = unsafe {
CStr: :from_ptr(msg.msg)
};
println ! ("id:{}message:{:?}", msg.id, s);
}
使 用cargo build 就可以编译出对应的动态库。C 语言的调用代码如下所示:
#include < stdio.h > #include < stdlib.h > #include < dlfcn.h > struct RustLogMessage {
int id;
char * msg;
};
int main() {
void * rust_log_lib;
void( * rust_log_fn)(struct RustLogMessage msg);
rust_log_lib = dlopen("./rust_log/target/debug/librust_log.so", RTLD_LAZY);
if (rust_log_lib != NULL) {
rust_log_fn = dlsym(rust_log_lib, "rust_log");
} else {
printf("load so library failed.\n");
return 1;
}
for (int i = 0; i < 10; i++) {
struct RustLogMessage msg = {
id: i,
msg: "string in c\n",
};
rust_log_fn(msg);
}
if (rust_log_lib != NULL) dlclose(rust_log_lib);
return EXIT_sUCCESS;
编 译 命 令 为gcc main.c -ldl。执行程序之前,要保证动态链接库和可执行程序之间 的相对路径关系是正确的,然后执行。可见,参数正确地在Rust 和 C 之 间 传 递 了 。
文档
Rust 也支持使用注释来编写规范的文档。可以使用rustdoc 工具把源码中的文档提取出来,生成易读的HTML 等格式。在cargo 里面可以用cargo doc 命令生成文档。
普通的注释有两种,一种是用//开头的,是行注释,一种是/* *1,是块注释。这 些注释不会被视为文档的一部分。特殊的文档是//1、//!、/**… 1、/!… */,它们会被视为文档。
跟 attribute 的规则类似:用///开头的文档被视为是给它后面的那个元素做的说明; //!开头的文档被视为是给包含这块文档的元素做的说明。 /**… * / 和 / * ! … * / 也 是 类似的。示例如下:
mod foo {
//!这块文档是给‘foo` 模块做的说明
///这块文档是给函数‘f` 做的说明
fn f() {
//这块注释不是文档的一部分
}
}
文档内部支持markdown 格式。可以使用#作为 一 级标题。比如标准库中常用的几种 标题 :
///#Panics
///#Errors
/ 1 / #Safety
///#Examples
文档中的代码部分要用符号把它们括起来。代码块应该用括起来。比如:
///
///let mut vec =Vec::with_capacity(10);
///
/ 11 //The vector contains no items,even though it has capacity for more
/ 1 / assert_eq ! (vec.len(), 0);
///
Rust 文档里面的代码块,在使用cargo test 命令时,也是会被当做测试用例执行的。这个设计可以在很多时候检查出文档和代码不对应的情况。
如果文档太长,也可以写在单独的markdown 文件中。如果在单独的文件中写文档,就 不需要再用///或者//!开头了,直接写内容就可以。然后再用一个attribute来指定给对 应的元素:
# ! [feature(external_doc)]# [doc(include = "external-doc.md")] pub struct MyAwesomeType;
测 试
Rust 内置了一套单元测试框架。单元测试是一种目前业界广泛使用的,可以显著提升代 码可靠性的工程管理手段。Rust 里面的单元测试代码可以直接和业务代码写在一个文件中, 非常有利于管理,方便更新。执行单元测试也非常简单,一条cargo test命令即可。
一般情况下,如果我们新建一个library项目,cargo工具会帮我们在src/lib.rs 中自 动生成如下代码:
# [cfg(test)] mod tests {# [test] fn it_works() {}
}
这就是最基本的单元测试框架。下面详细介绍一下这里面的各个要素。
首先,Rust 里面有一个特殊的attribute, 叫作#[cfg] 。 它主要是用于实现各种条件编 译。比如#[cfg(test)] 意思是,这部分代码只在test 这个开关打开的时候才会被编译。
它还有更高级的用法,比如
# [cfg(any(unix, windows))]# [cfg(all(unix, target_pointer_width = "32"))]# [cfg(not(foo))]
# [cfg(any(not(unix), all(target_os = "macos", target_arch = "powerpc")))]我们还可以自定义一些功能开关。比如在Cargo.toml中加入这样的代码: [features]#默认开启的功能开关
default = []#定义一个新的功能开关,以及它所依赖的其他功能#我们定义的这个功能不依赖其他功能,默认没有开启
my_feature_name = []之后就可以在代码中使用这个功能开关,某部分代码可以根据这个开关的状态决定编译还是不编译:
# [cfg(feature = "my_feature_name")] mod sub_module_name {}
这个开关究竟是开还是关,可以通过编译选项传递进去:
cargo build --features "my_feature_name"
当我们使用cargo test 命令的时候,被#[cfg(test)] 标记的代码就会被编译执 行 ; 否 则 直 接 被 忽 略 。我们还是用一个示例来说明。我们现在准备实现一个辗转相除法求最大公约数的功能。 新建一个名叫gcd 的项目:
cargo new --lib gcd
辗转相除法的细节就不展开了。实现代码如下所示:
pub fn gcd(a: u64, b: u64) - >u64 {
let(m(a(ut), b) 1, mut g) =
if a < b {} else { (b, a)
};
while 1 != 0 {
let m = g 81;
g = 1;
1 = m;
}
return g;
}
接下来添加一个最基本的测试 :
# [cfg(test)] mod tests {# [test] fn it_works() {
assert_eq ! (gcd(2, 3), 1);
}
}
使用cargo test 命令执行这个测试。这一次发生了编译错误,编译器找不到gcd 这个函 数。这是因为我们把测试用例写在了一个单独的模块中,在子模块中并不能直接访问父模块 中的内容。在mod 内部加一句use gcd; 或者use super:😗;可以解决这个问题。
Compiling gcd v0.1.0(file:///projects/gcd)
Finished dev [unoptimized +debuginfo]target(s)in 2.33 secs Running target/debug/deps/gcd-1658b34blde16a01
running 1 test
test tests::it_works ..ok
test result:ok.1 passed;0 failed;0 ignored;0 measured;0 filtered out Doc-tests gcd
running 0 tests
test result:ok.0 passed;0 failed;0 ignored;0 measured;0 filtered out
打印出来的结果非常清晰易读。前面一部分是执行测试模块中的测试用例的结果,后面 一部分是执行文档中的测试用例的结果。1 passed 代表通过了1个测试。0 failed 代表 失败了0个测试。0 ignored 代表忽略了0个测试。
用户可以用#[ignore] 标记测试用例,暂时忽略这个测试。比如:
# [test]# [ignore] fn it_works() {
assert_eq ! (gcd(2, 3), 1);
}
// measured代表跑了0个benchmark性能测试。我们可以用# [bench]添加性能测试用例:# [cfg(test)] mod tests {
use super: :*;
use self: :test: :Bencher;# [bench] fn big_num(b: &mut Bencher) {
b.iter( | l gcd(12345, 67890))
}
}
这个功能目前还没有稳定,需要用户在当前crate中开启feature gate:
#![feature(test)]
extern crate test;
然后使用cargo bench就可以执行这个性能测试。这时就可以看到测试结果中有1 measured的结果。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)