目录
Rust 工具练手
笔者工作中,需要对流量进行统计分析,以往同事分析时使用python脚本,有好处,可以实时更改;但是为了折腾一下,笔者采用rust重构一下这个工具,并且添加一些其他功能。
本文对代码不做深入分析,工具比较简单,三小时开发完初版,发现在这个工具的代码结构上,与c很相似,不同于C++的面向对象概念。但是开发方式上比c方便很多,因此借由此工具,着重分析面向过程编程中Rust和C的区别以及优缺点。
需要的伙伴可以查看源码
rust包管理仓库
以前在开发C/C++时,缺什么都是自己写,需要网络通信了,写一个 class Network或者int connect();后来接触python写脚本,发现可以pip install requests。再后来接触android,发现万物皆可import
//邮件功能包
implementation 'com.sun.mail:android-mail:1.6.6'
implementation 'com.sun.mail:android-activation:1.6.6'
//mqtt库
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
//二维码功能包
implementation 'com.google.zxing:core:3.5.2'
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
Rust也是如此,但是Rust相比其它更方便:Rust 使用一个官方的工具:Cargo,它集成了:
- 构建工具(build)
- 依赖管理器(dependency management)
- 包管理器(package manager)
- 发布工具(publish to crates.io)
使用一个cargo可以完成所有构建和依赖相关的工作。
crate定义
cargo从crates.io下载功能包,实际下载的是一个 .crate 文件,本质上就是一个压缩包(.tar.gz 格式)。解压后可以发现,就是包的源码。
然后,我们就引出一个概念:Crate
在 Rust 中:
- Crate 是最小的编译单位。
- 每一个 Rust 项目都是一个 crate。
- 可以写一个 crate 供别人使用,也可以依赖别人写的 crate。
例如:我的项目使用了7个别人的crate
csv = "1.3"//用于读写 CSV(逗号分隔值)文件
serde = { version = "1", features = ["derive"] }//Rust 中最常用的序列化/反序列化框架
serde_json = "1.0"//配合 serde 使用,处理 JSON 数据
rayon = "1.8"//数据并行处理库
reqwest = { version = "0.11", features = ["json", "blocking"] }//HTTP 客户端库
indicatif = "0.17"//命令行进度条和状态指示器
dotenvy = "0.15"//用于加载 .env 文件中的环境变量
使用 cargo build 或 cargo build --release 编译项目时,Cargo 会将所有依赖的代码(包括自己写的 + 所有 crate)编译进最终的可执行文件或库文件中。
所有的crate依赖形式都是源码形式,对于自己项目要求细粒度管理的人可以自由的先查看,修改,调试。并且所有的crate都是从官防仓库下载,统一,高可用,安全。
Rust打包策略
Rust 在构建时的哲学是:
一旦编译,所有依赖都打包进去,运行时就不需要额外的库(除了特定的动态库除外)。
这样的好处有很多:
- 部署方便,不用担心某个库没装
- 可移植性很高
- 编译时检查所有类型和依赖,运行时更安全
异常情况处理
C:返回错误码
在C中处理异常时,一般采用返回错误码(error code),设置全局变量 errno,使用手动设置错误码来处理异常。
C++:try/catch
C++采用try/catch/throw进行异常处理:
try {
throw std::runtime_error("Something went wrong");
} catch (const std::runtime_error& e) {
std::cout << "捕获异常: " << e.what() << std::endl;
}
在try/catch的处理思想中,编译器将try和catch的相关信息保存在异常处理表里,在进入异常分支后开始匹配catch。
Rust:Result&Option
在Rust中,使用Result和Option来处理异常。
本质上来说,Rust采用的方式类似于C中的返回错误码,因为Option 和 Result 类型在 Rust 中属于标准库中定义的枚举类型(enum):
enum Option<T> {//用于表示可能存在或不存在的值
Some(T),
None,
}
enum Result<T, E> {//用于表示可能成功或失败的操作结果
Ok(T),
Err(E),
}
使用上述两个类型,Rust会在编译前强制类型检查,强制处理分支,来保证安全。
例如:
//c
int open_file(const char* path) {
FILE* f = fopen(path, "r");
return f != NULL; // 返回 1 表示成功,0 表示失败
}
int main() {
int file = open_file("config.txt");
// ❌ 误把 file 当成文件句柄使用,实际上它只是一个返回码
fread(..., file); // 错误用法,但编译器不会报错
}
//rust
fn open_file(path: &str) -> Result<File, String> {
File::open(path).map_err(|e| e.to_string())
}
fn main() {
let file: Result<File, String> = open_file("config.txt");
// ❌ 不能直接使用 `file`,编译器会报错:
// error[E0308]: mismatched types
let content = read_file(file); // ❌必须先匹配 Ok 或 Err
// ✅ 正确做法:
match file {
Ok(f) => {
let content = read_file(f);
}
Err(e) => {
eprintln!("打开文件失败:{}", e);
}
}
}
- Result<File, String> 明确表明:这个值可能失败。
- 如果不处理错误,Rust 编译器会拒绝编译。
- 如果误用错误类型当作正常类型使用,Rust 编译器也会拒绝。
Rust Result vs C error code
特性 | Rust Result<T, E> | C 错误码(error code)方式 |
---|---|---|
类型安全 | ✅ 强制类型检查,防止误用 | ❌ 只是数字/宏,不具备类型信息 |
编译器强制处理 | ✅ 必须处理(除非 .unwrap() 或忽略) | ❌ 可忽略返回值,极易出错 |
可读性和语义性 | ✅ Ok(value) / Err(error) 明确语义 | ❌ 通常是 0 / -1 / NULL ,不直观 |
自定义错误信息 | ✅ 可以是任何类型,如 String 、enum 等 | ❌ 一般靠 errno + strerror() 辅助 |
链式处理/组合 | ✅ 用 ? 、.map() 、.and_then() 等方便组合 | ❌ 手动检查,嵌套 if / goto 结构复杂 |
控制流集成(语法糖) | ✅ ? 操作符可自动传播错误 | ❌ 需要每步都手动返回错误码 |
unwrap方法
.unwrap() 是一个常用的方法,主要用于从 Option 或 Result 类型中提取出内部的值。如果值是合法的,它会返回内部的值;如果不是,它会导致程序panic(崩溃),
let maybe_value: Option<i32> = None;
let value = maybe_value.unwrap(); // 程序崩溃,panic: called `Option::unwrap()` on a `None` value
let result: Result<i32, &str> = Err("出错了");
let value = result.unwrap(); // 程序崩溃,panic: called `Result::unwrap()` on an `Err` value: "出错了"
let v = maybe_value.expect("必须有值,不能为 None");//用 expect() 提供错误提示,依然程序崩溃
所以unwrap主要用在调试阶段,release阶段应该强制处理分支,使用match 语句手动处理:
//使用 match 语句手动处理
match maybe_value {
Some(v) => println!("值是 {}", v),
None => println!("没有值"),
}
//用 unwrap_or() 提供默认值:
let v = maybe_value.unwrap_or(0); // 如果是 None,就返回 0
//如果是 Ok(v),返回 v
//如果是 Err(e),调用闭包来生成默认值(只有发生错误时才调用)
let value = some_result.unwrap_or_else(|e| {
log_error(e);
generate_default()
});
PcapRacer:
let api_url = env::var("API_URL").unwrap_or_else(|_| {
String::from("")//采用闭包+模式匹配,|_|不使用err变量,赋一个默认值
});