Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6
1334 字
7 分钟
我用 Rust 重写了一个命令行工具

TL;DR#

把一个日常使用的 Python 脚本用 Rust 重写了,性能从 2.3s 提升到 0.04s,内存占用从 45MB 降到 3MB。这篇文章记录了整个迁移过程和踩过的坑。

起因:一个越来越慢的脚本#

我有个 Python 脚本,用来批量处理 Markdown 文件:提取 frontmatter、统计字数、生成目录。最开始只有几十个文件时还挺快,但随着文章越来越多(现在 200+ 篇),每次运行都要等好几秒。

$ time python process.py
处理完成:215 个文件
real 0m2.347s
user 0m2.156s
sys 0m0.187s

虽然 2 秒不算太慢,但每次修改文章后都要跑一遍,一天下来要等好几分钟。更要命的是,这个脚本还会被 CI/CD 调用,拖慢了整个构建流程。

为什么选 Rust#

最开始想过几个方案:

  1. 优化 Python 代码:试过用 multiprocessing,但 GIL 和进程开销反而更慢
  2. 换成 Go:写起来确实快,但二进制文件太大(15MB+)
  3. 试试 Rust:听说性能好,正好学习一下

最终选 Rust 的原因:

  • 零成本抽象,性能接近 C
  • 内存安全,不用担心段错误
  • 生态成熟,有很多现成的库
  • 编译后的二进制小(3MB 左右)

实现过程#

1. 项目初始化#

cargo new md-processor --bin
cd md-processor

添加依赖(Cargo.toml):

[dependencies]
walkdir = "2" # 遍历目录
gray_matter = "0.2" # 解析 frontmatter
regex = "1" # 正则匹配
rayon = "1.7" # 并行处理
serde = { version = "1", features = ["derive"] }
serde_json = "1"

2. 核心逻辑#

Python 版本的核心代码:

def process_file(path):
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
# 提取 frontmatter
matter = frontmatter.loads(content)
# 统计字数
text = re.sub(r'[^\\u4e00-\\u9fa5a-zA-Z0-9]', '', matter.content)
word_count = len(text)
return {
'path': path,
'title': matter.get('title', ''),
'words': word_count
}
# 串行处理
results = [process_file(f) for f in files]

Rust 版本:

use rayon::prelude::*;
use std::fs;
#[derive(Debug, Serialize)]
struct FileInfo {
path: String,
title: String,
words: usize,
}
fn process_file(path: &Path) -> Result<FileInfo> {
let content = fs::read_to_string(path)?;
// 解析 frontmatter
let matter = gray_matter::Matter::<gray_matter::engine::YAML>::new();
let parsed = matter.parse(&content);
// 统计字数(只保留中英文和数字)
let text: String = parsed.content
.chars()
.filter(|c| c.is_alphanumeric() || (*c >= '\\u{4e00}' && *c <= '\\u{9fa5}'))
.collect();
Ok(FileInfo {
path: path.display().to_string(),
title: parsed.data
.as_ref()
.and_then(|d| d.get("title"))
.and_then(|t| t.as_str())
.unwrap_or("")
.to_string(),
words: text.len(),
})
}
fn main() -> Result<()> {
let files: Vec<_> = WalkDir::new("posts")
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension() == Some(OsStr::new("md")))
.collect();
// 并行处理
let results: Vec<_> = files
.par_iter()
.filter_map(|entry| process_file(entry.path()).ok())
.collect();
println!("处理完成:{} 个文件", results.len());
Ok(())
}

3. 踩过的坑#

坑1:字符串处理

Rust 的字符串是 UTF-8 编码,不能直接按索引访问。一开始写成:

let char = content[0]; // ❌ 编译错误

正确做法:

let char = content.chars().next(); // ✅

坑2:错误处理

Python 可以随便 try-except,Rust 必须显式处理每个 Result。一开始写了很多 unwrap(),结果遇到异常文件就 panic。

改用 ? 操作符和 filter_map 优雅处理:

let results: Vec<_> = files
.par_iter()
.filter_map(|entry| process_file(entry.path()).ok()) // 忽略错误
.collect();

坑3:并行处理的开销

一开始直接用 rayon 并行,发现小文件反而变慢了。原因是线程创建和调度的开销。

解决方案:只在文件数量 > 50 时才并行:

let results = if files.len() > 50 {
files.par_iter().filter_map(|e| process_file(e).ok()).collect()
} else {
files.iter().filter_map(|e| process_file(e).ok()).collect()
};

性能对比#

基准测试#

测试环境:

  • CPU: AMD Ryzen 7 5800H
  • RAM: 16GB
  • 文件数:215 个 Markdown 文件
  • 总大小:约 3.2MB
指标PythonRust提升
执行时间2.347s0.043s54x
内存占用45MB2.8MB16x
二进制大小-3.1MB-
启动时间0.18s0.001s180x

为什么这么快?#

  1. 编译优化:Rust 编译时做了大量优化(内联、循环展开等)
  2. 零拷贝:字符串处理时避免了不必要的内存分配
  3. 并行处理rayon 自动利用多核 CPU
  4. 无 GC:没有垃圾回收的停顿

实际收益#

开发体验#

优点:

  • 编译器非常严格,很多 bug 在编译期就被发现
  • 类型系统强大,重构很安全
  • 性能确实快,用起来很爽

缺点:

  • 学习曲线陡峭,所有权系统需要时间理解
  • 编译时间长(首次编译 2 分钟+)
  • 生态虽然成熟,但不如 Python 丰富

CI/CD 优化#

之前构建流程:

拉取代码 (5s) → 安装依赖 (15s) → 处理文件 (2.3s) → 构建 (30s)
总计:52.3s

现在:

拉取代码 (5s) → 下载二进制 (1s) → 处理文件 (0.04s) → 构建 (30s)
总计:36s

每次构建节省 16 秒,一天跑 20 次就是 5 分钟。

经验总结#

什么时候该用 Rust#

适合:

  • 性能敏感的工具(CLI、数据处理)
  • 需要长期维护的项目
  • 对内存占用有要求
  • 需要跨平台分发

不适合:

  • 快速原型开发
  • 频繁变更需求的项目
  • 团队没有 Rust 经验
  • 简单的一次性脚本

给新手的建议#

  1. 从小项目开始:不要一上来就重写大型项目
  2. 多看文档:Rust Book 和 Rust by Example 写得很好
  3. 善用编译器:错误信息很详细,认真读
  4. 拥抱所有权:不要和编译器对抗,理解它的设计哲学
  5. 利用生态:crates.io 上有很多优秀的库

后续计划#

这次重写让我尝到了甜头,接下来打算:

  1. 把其他几个 Python 脚本也迁移过来
  2. 学习 async/await,处理网络请求
  3. 尝试用 Rust 写个 Web 服务(Axum 框架)

代码仓库#

完整代码已开源:github.com/example/md-processor

欢迎 star 和提 issue!


更新记录:

  • 2025-12-25:初版发布
  • 2025-12-26:添加了错误处理优化
  • 2025-12-27:修复了中文字符统计的 bug
我用 Rust 重写了一个命令行工具
https://sylviz.cn/posts/14/
作者
kiwi
发布于
2025-12-25
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00