闪电疯狂赛车
91.44M · 2026-03-23
上个月我接了个私活,写一个高并发日志采集服务。本来用 Python + asyncio 三天搞定,但甲方要求"内存占用必须控制在 50MB 以内,单机吞吐 10 万条/秒"。Python 再怎么优化也够呛,于是硬着头皮开了个 Rust 项目。
说实话,之前看 Rust 的所有权机制觉得"这有啥难的",真正写起来才知道——编译器是真的会骂人。这篇文章记录一下我这个 Python 老兵转 Rust 第一周的真实体验,不吹不黑。
值得,但别指望一周入门。我写了 8 年 Python,第一周有效代码产出大概是 Python 的 1/5,剩下时间都在跟编译器吵架。但一旦编译通过,程序的稳定性和性能确实让我震到了——同样的日志采集逻辑,Rust 版内存占用 12MB,Python 版 180MB。
这倒是出乎我意料。Rust 的工具链比 Python 生态干净太多了。
# 安装 Rust 工具链
curl --proto '=https' --tlsv1.2 -sSf | sh
# 验证安装
rustc --version
cargo --version
# 创建项目
cargo new log_collector
cd log_collector
不用管 virtualenv、conda、pyenv 这些东西,rustup 一把梭。cargo 同时是包管理器、构建工具、测试运行器,相当于 pip + setuptools + pytest 合体。
我写的第一段 Rust 代码,完全是 Python 思维:
fn main() {
let msg = String::from("hello log");
let processed = process(msg);
println!("原始消息: {}", msg); // 编译报错!
println!("处理结果: {}", processed);
}
fn process(s: String) -> String {
format!("[LOG] {}", s)
}
编译器直接给我甩了一脸红字:
error[E0382]: borrow of moved value: `msg`
--> src/main.rs:4:33
|
2 | let msg = String::from("hello log");
| --- move occurs because `msg` has type `String`
3 | let processed = process(msg);
| --- value moved here
4 | println!("原始消息: {}", msg);
| ^^^ value borrowed here after move
Python 里把变量传给函数后还能继续用,因为 Python 用的是引用计数 + GC。Rust 的所有权机制规定:一个值在同一时刻只能有一个"主人",msg 传给 process 后所有权就转移了,原来的 msg 直接失效。
修复方式有三种:
// 方案 1:传引用(最常用)
fn process(s: &String) -> String {
format!("[LOG] {}", s)
}
// 调用:let processed = process(&msg);
// 方案 2:克隆(简单粗暴,有性能开销)
let processed = process(msg.clone());
// 方案 3:用完再还回来(不推荐,丑)
fn process(s: String) -> (String, String) {
let result = format!("[LOG] {}", s);
(s, result)
}
搞明白这张图之后,后面踩的坑少了很多:
graph TD
A[创建值 let s = String::from] --> B{怎么传递?}
B -->|移动 move| C[所有权转移<br/>原变量失效]
B -->|不可变借用 &s| D[只读访问<br/>可以有多个]
B -->|可变借用 &mut s| E[可读写<br/>同时只能有一个]
C --> F[新所有者负责释放]
D --> G[借用结束后<br/>原所有者继续有效]
E --> G
用 Python 的话翻译一下:
del 原变量后把值给新变量所有权搞明白后,我以为自己毕业了。结果写到结构体持有引用的时候,又被生命周期(Lifetime)按在地上摩擦。
// 这段代码编译不过
struct LogEntry {
level: &str,
message: &str,
}
编译器要求你标注生命周期:
struct LogEntry<'a> {
level: &'a str,
message: &'a str,
}
fn create_entry<'a>(level: &'a str, msg: &'a str) -> LogEntry<'a> {
LogEntry {
level,
message: msg,
}
}
fn main() {
let entry = create_entry("INFO", "server started");
println!("[{}] {}", entry.level, entry.message);
}
那个 'a 看着吓人,其实就是告诉编译器:"这个结构体里的引用,活得不会比外面的数据更久。"
我的建议:刚入门别硬刚生命周期。 结构体里尽量用 String 而不是 &str,等写了几千行代码有感觉了再优化:
// 新手友好版,先跑起来再说
struct LogEntry {
level: String,
message: String,
}
Python 里处理错误基本靠 try/except,很容易忘记处理某些异常。Rust 的 Result + 模式匹配直接在编译期逼你把所有情况都考虑到:
use std::fs;
use std::io;
fn read_config(path: &str) -> Result<String, io::Error> {
fs::read_to_string(path)
}
fn main() {
match read_config("config.toml") {
Ok(content) => {
println!("配置内容: {}", content);
}
Err(e) if e.kind() == io::ErrorKind::NotFound => {
println!("配置文件不存在,使用默认配置");
}
Err(e) => {
eprintln!("读取配置失败: {}", e);
std::process::exit(1);
}
}
}
还有个 ? 操作符,相当于自动 unwrap + 向上抛错,写起来特别爽:
fn load_and_parse(path: &str) -> Result<Vec<String>, io::Error> {
let content = fs::read_to_string(path)?; // 出错自动返回 Err
let lines: Vec<String> = content
.lines()
.map(|l| l.to_string())
.collect();
Ok(lines)
}
Python 里要实现类似效果,得自己写一堆 try/except 嵌套,或者上 returns 这种第三方库。Rust 原生支持,而且零运行时开销。
同样的逻辑——读取 100 万行日志文件,按关键词过滤,写入新文件:
| 指标 | Python 3.12 | Rust (release) | 差距 |
|---|---|---|---|
| 执行时间 | 4.2s | 0.18s | 23x |
| 内存峰值 | 380MB | 8.5MB | 45x |
| 二进制大小 | 需要解释器 | 2.1MB 单文件 | - |
| CPU 占用 | 单核 100% | 单核 35% | - |
测完这组数据我人傻了。release 模式的 Rust 性能碾压 Python 是意料之中,但差距这么大还是超出预期。而且编译出来就是单个可执行文件,不需要装运行时,部署体验比 Python 好太多。
难受的点:
String、&str、&String、Cow<str>,搞不清什么时候用哪个tokio 的学习曲线挺陡爽到的点:
cargo 工具链体验极好,比 pip 强几个量级String 代替 &str,用 clone() 代替借用,先跑起来cargo clippy。Rust 版的 pylint,能教你写出更地道的 Rust 代码# 安装并运行 clippy
rustup component add clippy
cargo clippy
一周下来我还不敢说自己入门了。但至少能写出编译通过的几百行代码,这在 Rust 社区已经算"活下来了"。那个日志采集服务最终交付了,甲方看到内存占用 12MB 的报告,直接多给了我 20% 的费用。
Rust 不会替代我日常用 Python 写脚本,但在需要性能和可靠性的场景,它已经进我工具箱了。