Rust 入门:一个写了 6 年 Python 的人,被编译器骂了三天

上个月我接了一个小活,帮朋友写个高并发的文件处理服务。本来想直接上 Python + asyncio,但跑了个 benchmark 发现单核 CPU 吃满了还是不够快,内存也飘到 2G 以上。朋友说要不试试 Rust?我说行,反正最近也想学。

然后我就被编译器骂了整整三天。

这篇文章不是 Rust 教程(官方的 The Book 比我写得好一万倍),而是一个 Python 程序员真实的入门体验------哪些概念让我抓狂,哪些地方豁然开朗,以及我踩过的那些坑。

先说结论

维度 Python 体感 Rust 体感
上手速度 半天写 CRUD 三天和编译器搏斗
编译器友好度 没编译器这回事 严格但错误提示极好
性能 够用就行,不够上 C 扩展 默认就很快,不用额外优化
内存管理 GC 自动搞定 所有权系统,手动但安全
生态 pip install 万物 crates.io 生态在快速成长
写代码的快乐 写得爽,跑得慢 debug 才痛苦 写得痛苦,跑起来很安心
适合场景 脚本、Web、数据分析、AI 系统编程、CLI工具、高性能服务

一句话总结:Rust 把痛苦前置到了编译期,Python 把痛苦后置到了运行时。 两种哲学,没有高下。

为什么一个 Python 程序员要学 Rust

说实话一开始我是拒绝的。Python 写了 6 年,FastAPI + SQLAlchemy 一把梭,日常需求根本不需要 Rust。但这两年有几件事改变了我的想法。

Python 的性能天花板是真实存在的。之前写一个日志解析工具,10GB 的文件 Python 跑了 40 多分钟,用 Rust 重写核心逻辑后不到 2 分钟。

CLI 工具分发也很痛。Python 打包成可执行文件,要么用 PyInstaller 打出 200MB 的包,要么让用户自己装 Python 环境。Rust 编译出来就是一个二进制,扔过去就能跑。

还有就是 2026 年了,很多新工具都是 Rust 写的------ruff、uv、ripgrep、fd、bat......Python 社区自己都在用 Rust 重写工具链。不懂 Rust 的话连开 PR 都有点不好意思。

第一道坎:所有权(Ownership)

Python 程序员最先被绊倒的地方,100% 是所有权。

在 Python 里你可以随便这么写:

python 复制代码
def process(data):
    result = do_something(data)
    print(data)  # data 还能用,没毛病
    return result

names = ["alice", "bob"]
process(names)
print(names)  # 还能用,Python 传的是引用

你从来不需要想「这个变量还能不能用」。但在 Rust 里:

rust 复制代码
fn process(data: Vec<String>) {
    // data 的所有权转移到了这个函数里
    println!("{:?}", data);
}

fn main() {
    let names = vec!["alice".to_string(), "bob".to_string()];
    process(names);
    // println!("{:?}", names);  // 编译报错!names 已经被 move 了
}

第一次看到这个报错我是懵的:

go 复制代码
error[E0382]: borrow of moved value: `names`

什么叫 moved value?我明明只是传了个参数啊?

花了一个下午才搞明白:每个值在同一时刻只有一个所有者。 值被传到函数里,所有权就转移了,原来的变量就「死」了。

解决办法有三种,我现在的选择优先级是这样的:

rust 复制代码
// 方案一:借用(最常用)
fn process(data: &Vec<String>) {
    println!("{:?}", data);
}

fn main() {
    let names = vec!["alice".to_string(), "bob".to_string()];
    process(&names);  // 传引用,不转移所有权
    println!("{:?}", names);  // 还能用!
}

// 方案二:克隆(简单粗暴,有性能开销)
fn main() {
    let names = vec!["alice".to_string(), "bob".to_string()];
    process(names.clone());  // 复制一份传过去
    println!("{:?}", names);  // 原来的还在
}

// 方案三:返回所有权(偶尔用)
fn process(data: Vec<String>) -> Vec<String> {
    println!("{:?}", data);
    data  // 用完再还回去
}

我的建议:先无脑用借用 &,编译器报错了再想别的办法。 Rust 编译器的错误提示真的很好,不但告诉你哪错了,还告诉你怎么改。

第二道坎:生命周期(Lifetime)

所有权是第一道关卡,生命周期是第二道。

我第一次遇到生命周期问题是写一个简单的函数,返回两个字符串中较长的那个:

rust 复制代码
// 这样写会报错
fn longer(s1: &str, s2: &str) -> &str {
    if s1.len() > s2.len() { s1 } else { s2 }
}

编译器说:

less 复制代码
error[E0106]: missing lifetime specifier
help: consider introducing a named lifetime parameter

得加上生命周期标注:

rust 复制代码
fn longer<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() > s2.len() { s1 } else { s2 }
}

这个 'a 的意思是:返回值的生命周期和输入参数一样长。说白了就是告诉编译器「别担心,我返回的引用不会指向已经被释放的内存」。

说实话这个概念我理解了三天。后来想通了一个类比:生命周期就是 Rust 版的「这个引用保质期到什么时候」标签。 Python 不需要这个是因为有 GC 兜底,Rust 没有 GC,所以得你自己标明。

好消息是大部分情况编译器能自己推断,不需要手动标。我写了一个月 Rust,真正需要手动写 'a 的地方不超过 5 次。

第三道坎:错误处理

Python 的错误处理:

python 复制代码
try:
    data = open("config.json").read()
    config = json.loads(data)
except FileNotFoundError:
    print("文件不存在")
except json.JSONDecodeError:
    print("JSON 格式错误")

Rust 的错误处理:

rust 复制代码
use std::fs;
use serde_json::Value;

fn load_config() -> Result<Value, Box<dyn std::error::Error>> {
    let data = fs::read_to_string("config.json")?;
    let config: Value = serde_json::from_str(&data)?;
    Ok(config)
}

fn main() {
    match load_config() {
        Ok(config) => println!("loaded: {:?}", config),
        Err(e) => eprintln!("出错了: {}", e),
    }
}

那个 ? 是语法糖,相当于「如果出错就提前返回错误」。刚开始我觉得比 try-except 啰嗦,写了一周之后反而觉得这种方式更好------你不可能「忘记」处理错误,因为编译器不让你忽略 Result 类型。

在 Python 里我经常犯的错就是漏掉某个异常,然后生产环境半夜炸了。Rust 从根上堵死了这个问题。

踩坑记录

坑 1:String 和 &str 的区别

Python 里只有一种字符串 str,Rust 里有两种:

  • String:堆上分配的、可变的、拥有所有权的字符串
  • &str:字符串的引用/切片,不拥有所有权

简单记:函数参数用 &str(更通用),结构体字段用 String(拥有数据)。 需要从 &strString.to_string().to_owned(),反过来 String&str 直接 &my_string 就行。

坑 2:cargo 依赖版本冲突

有一次我同时引入了两个 crate,它们依赖了不同版本的 tokio,编译直接报了一堆看不懂的类型错误。排查了半天才发现是版本冲突。

解决方案:在 Cargo.toml 里统一指定大版本,然后 cargo update 让它自己解决。实在不行就 cargo tree 看依赖树,找出冲突的源头。

bash 复制代码
# 查看依赖树,找到谁依赖了旧版本
cargo tree -d

坑 3:异步运行时不能嵌套

习惯了 Python 的 asyncio.run(),我在 Rust 里也想在异步函数里再起一个 runtime,结果直接 panic 了:

css 复制代码
Cannot start a runtime from within a runtime

在 tokio 里,你只能有一个运行时入口。嵌套调用要用 tokio::spawn,不能再建一个 runtime。这个坑我在 StackOverflow 上看了俩小时。

一个月后的真实感受

学 Rust 第一周:这什么鬼语言,编译器是不是针对我?

第二周:好吧,编译器说得对,是我的问题......

第三周:嗯,编译通过了就是能跑的,几乎没有运行时 bug,真爽。

第四周:回去写 Python 的时候开始怀念 Rust 的类型系统了。

Rust 编译器不是你的敌人,是你最严格的 code reviewer。 它在编译期帮你找到了 Python 里可能要到线上才暴露的 bug。痛苦是真的,但通过编译后的安心感也是真的。

给 Python 程序员的入门建议

先看 The Rust Programming Language(The Book)。 免费在线版就行,别买付费课程,真不需要。

第一个项目写 CLI 工具。 别上来就搞 Web 服务,用 clap 写个命令行工具,体验从零到产出一个可执行文件的那种爽感。

别怕 .clone() 初学阶段能编译通过最重要,性能优化是后面的事。到处 clone 不丢人。

Rustlings 练习题必做。 GitHub 上搜 rustlings,一共 90 多道小题,跟着做完对所有权和借用就有感觉了。

不要试图一次性搞懂所有概念。 生命周期、trait object、Pin、async 这些,用到的时候再学。

我现在的节奏是:业务逻辑和快速原型用 Python,性能敏感的模块和 CLI 工具用 Rust。两个语言互补,效率反而比只用一个高。

如果你也是 Python 背景想学 Rust,希望这篇踩坑记录能帮你少走点弯路。有问题评论区聊,我能回答的都会回。

相关推荐
2401_857865232 小时前
Python日志记录(Logging)最佳实践
jvm·数据库·python
AsDuang2 小时前
Python 3.12 MagicMethods - 54 - __rrshift__
开发语言·python
Bert.Cai2 小时前
Python字符串详解
开发语言·python
宸翰2 小时前
在VS code中如何舒适的开发Python
前端·python
m0_716667072 小时前
趣味项目与综合实战
jvm·数据库·python
m0_662577972 小时前
Python虚拟环境(venv)完全指南:隔离项目依赖
jvm·数据库·python
坐吃山猪2 小时前
Python项目一键创建
开发语言·python
纤纡.3 小时前
Python 实战:基于朴素贝叶斯的苏宁易购评价情感分析
开发语言·python·机器学习