wakamda's blog
Code,Think,Create
推送于 | 更新于 | Tags:

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 在构建时的哲学是:

一旦编译,所有依赖都打包进去,运行时就不需要额外的库(除了特定的动态库除外)。

这样的好处有很多:

  1. 部署方便,不用担心某个库没装
  2. 可移植性很高
  3. 编译时检查所有类型和依赖,运行时更安全

异常情况处理

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变量,赋一个默认值
});

Copyright 2025 wakamda