Rust 入门:一个写了 6 年 Python 的人,聊聊真实体验和踩坑

张开发
2026/4/5 15:04:59 15 分钟阅读

分享文章

Rust 入门:一个写了 6 年 Python 的人,聊聊真实体验和踩坑
上个月我接了个活写一个日志分析工具每天处理大概 2000 万行日志。一开始用 Python 写了个原型跑起来单核吃满、内存飙到 4G处理完一天的数据要 40 分钟。这玩意儿上线了不得被运维同事骂死正好 2026 年了身边搞基础设施的朋友基本人手一个 Rust 项目。我就想趁这个机会学一下于是花了三周从零开始用 Rust 重写了这个工具。这篇文章不是 Rust 教程官方的 The Book 写得比我好一万倍就是站在一个 Python 老用户的视角聊聊 Rust 哪里让我觉得爽、哪里让我想砸键盘以及最终的性能对比数据。先说结果值不值得学直接给结论如果你是 Python 程序员Rust 值得学但不要指望一周入门。重写后的对比数据指标Python 版Rust 版处理 2000 万行耗时38 分钟1 分 42 秒内存峰值3.8 GB220 MB二进制大小N/A需要解释器4.2 MBCPU 占用单核 100%4 核均匀分配开发耗时2 天12 天含学习时间22 倍的速度差距内存只有 Python 版的 1/17。开发时间多了 5 倍——但这包括了从零学 Rust 的时间熟练之后估计 3-4 天能写完。让 Python 程序员觉得爽的部分Cargo 是我用过最好的包管理器说真的Cargo 让我第一次觉得包管理可以这么顺。想想 Python 那边pip、virtualenv、pyenv、poetry、pdm、uv……光选哪个工具就能吵三天。Rust 只有一个 Cargo创建项目、管理依赖、编译、测试、发布全包了# 创建项目cargonew log-analyzercdlog-analyzer# 加依赖直接编辑 Cargo.toml 或者用命令cargoaddserde--featuresderivecargoaddrayoncargoaddclap--featuresderive# 编译运行cargorun--release# 跑测试cargotestCargo.toml长这样清清爽爽[package] name log-analyzer version 0.1.0 edition 2021 [dependencies] serde { version 1.0, features [derive] } rayon 1.10 clap { version 4.5, features [derive] }没有requirements.txt和pyproject.toml到底该用哪个的灵魂拷问没有虚拟环境忘了激活导致装错位置的抓狂。这一点 Rust 完胜。编译器就是最好的 Code Reviewerrustc 严格到变态但说实话——它救了我无数次。Python 里写出一个None引用的 bug可能到线上跑了三天才炸。Rust 在编译阶段就不让你过// Python 里你可能这么写// data get_data() # 可能返回 None// print(data.strip()) # 运行时才炸// Rust 里你必须处理 None 的情况fnprocess_line(line:Optionstr)-String{matchline{Some(content)content.trim().to_string(),NoneString::from((empty)),}}一开始我觉得这很烦每个可能为空的值都要match或者unwrap。但写了一周之后发现我的 Rust 代码几乎没有运行时错误。编译通过基本就能跑这种安全感是 Python 给不了的。模式匹配太香了Python 3.10 加了match语法但说实话用得很少。Rust 的模式匹配是深入骨髓的到处都在用usestd::collections::HashMap;#[derive(Debug)]enumLogLevel{Info,Warn,Error(String),// Error 可以携带额外信息}fnparse_log_line(line:str)-Option(LogLevel,str){letparts:Vecstrline.splitn(3, ).collect();matchparts.as_slice(){[_,INFO,msg]Some((LogLevel::Info,msg)),[_,WARN,msg]Some((LogLevel::Warn,msg)),[_,ERROR,msg]Some((LogLevel::Error(msg.to_string()),msg)),_None,// 解析失败就返回 None不会 panic}}fnmain(){letlinesvec![2026-04-15 INFO user logged in,2026-04-15 ERROR database connection timeout,malformed line,];letmutstats:HashMapstr,usizeHashMap::new();forlineinlines{matchparse_log_line(line){Some((LogLevel::Error(refdetail),_)){println!(发现错误: {},detail);*stats.entry(error).or_insert(0)1;}Some((LogLevel::Warn,_)){*stats.entry(warn).or_insert(0)1;}Some((LogLevel::Info,_)){*stats.entry(info).or_insert(0)1;}None{*stats.entry(unknown).or_insert(0)1;}}}println!(统计结果: {:?},stats);}每个分支都处理得明明白白漏掉任何一个分支编译器直接报错。写 Python 的时候我经常忘记处理边界情况Rust 不给你这个机会。让我想砸键盘的部分所有权和借用从入门到想放弃这是每个 Rust 新手必经的痛苦。Python 里你随便把变量传来传去谁也不管谁。Rust 里每个值都有一个所有者传出去了就没了fnmain(){letnameString::from(hello);letgreetingmake_greeting(name);// println!({}, name); // ❌ 编译错误name 已经被 move 了println!({},greeting);// ✅ 这个没问题}fnmake_greeting(n:String)-String{format!(Hi, {}!,n)}我第一天写 Rust 的时候编译器报了 47 个所有权相关的错误不夸张。后来总结了一个心智模型分享给同样从 Python 过来的朋友只读一下要修改它不再需要原值需要各自一份创建一个值要传给别的函数传引用 value传可变引用 mut value直接 moveclone 一份原值还能继续用同一时间只能有一个 mut原值不能再用了两份独立的值建议前三天别跟编译器较劲它让你clone你就clone先把东西跑起来再优化。字符串类型多到怀疑人生Python 里字符串就是str顶多分个bytes。Rust 里光常见的就有类型说明类比 PythonString堆上分配可变拥有所有权str最接近str字符串切片不可变引用类似字符串视图[u8]字节切片bytesOsString操作系统字符串os.fsencode()CStringC 兼容字符串ctypes里用的Cowstr写时克隆可以是借用也可以是拥有没有对应的我前两周写的代码到处是.to_string()、.as_str()、*s这种转换丑得一批。后来慢慢理解了函数参数尽量用str返回值和结构体字段用String基本能覆盖 80% 的场景。生命周期标注劝退率最高的知识点。写一个返回引用的函数时Rust 要你标注这个引用活多久// 编译器不知道返回的引用跟哪个参数的生命周期绑定// 所以你得告诉它fnlongera(s1:astr,s2:astr)-astr{ifs1.len()s2.len(){s1}else{s2}}fnmain(){letresult;lets1String::from(long string);{lets2String::from(hi);resultlonger(s1,s2);println!({},result);// ✅ s2 还活着}// println!({}, result); // ❌ s2 已经没了result 可能悬空}到现在我也没完全吃透复杂的生命周期场景。我的策略是能用String返回就不返回引用除非你很确定自己在做什么。真正让我留下来的Rayon 并行处理这是让我决定在合适场景用 Rust 的关键原因。Python 因为 GIL多线程做 CPU 密集任务基本是摆设。Rust 的 Rayon 库让并行化简单到离谱userayon::prelude::*;usestd::fs;fncount_errors_in_file(path:str)-usize{letcontentfs::read_to_string(path).unwrap_or_default();content.par_lines()// 就这一行把 lines() 换成 par_lines().filter(|line|line.contains(ERROR)).count()}fnmain(){letfiles:VecString(1..100).map(|i|format!(logs/app_{}.log,i)).collect();lettotal_errors:usizefiles.par_iter()// 并行遍历文件.map(|f|count_errors_in_file(f)).sum();println!(总共发现 {} 个错误,total_errors);}.lines()换成.par_lines().iter()换成.par_iter()并行就完成了。没有线程池配置没有concurrent.futures没有 GIL。而且 Rust 的所有权系统保证了你没法写出数据竞争的代码——编译器直接拦住。Python 多线程共享数据踩的那些坑在 Rust 里根本不存在。给 Python 程序员的入门建议磕了三周总结几条先看 The Rust Programming LanguageThe Book官方文档质量极高中文翻译也不错前三天别追求完美代码编译器让你clone()就clone()先跑起来用 Cursor 或 TRAE 写 RustAI 补全对 Rust 的支持现在很成熟尤其是生命周期标注它比我熟练多了从 CLI 小工具开始别上来就搞 Web 框架用clapserde写个命令行工具是最好的练手项目心态要对编译不过是正常的编译过了基本就对了。跟 Python 正好反过来——Python 是写着爽调试哭Rust 是写着哭跑着爽什么时候该用 Rust 而不是 Python场景选择理由快速原型/脚本/数据分析Python开发速度快 10 倍API 服务中小规模Python (FastAPI)生态成熟开发效率高CPU 密集型数据处理Rust性能差距太大CLI 工具Rust编译成单二进制分发方便需要长期运行的服务Rust内存安全没有 GC 停顿AI/ML 相关Python生态没得比小结三周下来我不会说Rust 是最好的语言这种话。但确实体会到了一点需要性能和安全的时候编译通过就能放心跑这种感觉真的很难回去。日志分析工具现在稳定跑了两周内存稳定在 200MB 左右处理时间从 38 分钟降到不到 2 分钟。运维同事从想打我变成了问我能不能再写几个类似的工具。如果你也是 Python 背景想试试 Rust就一句话别怕编译器的红字那是它在帮你。

更多文章