我有一支技术全面、经验丰富的小型团队,专注高效交付中等规模外包项目,有需要外包项目的可以联系我
Python 与 Rust 风格截然不同,但组合起来却意外合拍。在讨论如何把二者拼在一起之前,先快速认识一下 Rust。你大概听过它的名号,但未必摸清了它的“脾气”。
什么是 Rust?
Rust 属于低层语言,程序员需要更贴近机器的真实工作方式来思考。
举例:整数类型按
字节位宽区分,对应 CPU 支持的类型。直觉上你或许会说:在 Rust 里 a + b
就是“一条机器指令”。但编译链太复杂,这种说法只在粗略意义上成立。
Rust 的目标是零成本抽象:许多高级抽象在运行期会被编译器“抹平”,不额外付费。
例如:对象默认分配在栈上(除非你显式要求堆分配),因此创建原生对象没有运行期开销(尽管初始化仍然需要)。
最后,Rust 是内存安全语言。别的语言也可能提供内存安全或零成本抽象,但往往不是同一类。 内存安全并不代表“永不违规”,而是仅有两种途径会出事:
标准库里确实有一些 unsafe
,但远少于多数人的想象。这并不削弱前述结论:除非你(少数场景里)必须手写 unsafe
,大多数违规来自底层基础设施而非业务面。
为什么要用 Rust?
Rust 诞生是为同时追求
效率与内存安全。在互联环境里,这个目标越来越关键。
典型场景:底层协议解析。输入常来自不可信源,既要快,又要稳。
这听起来像浏览器在做的事?没错。Rust 来自 Mozilla 基金会,最初就是为了改进 Firefox。
如今不止浏览器:常见的微服务同样要快速解析不可信数据并且保证安全。
示例:统计字符
为理解“把 Rust 包一层给 Python 用”的例子,我们先设定一个问题,满足:
具体问题:判断某字符在字符串里是否出现超过 X 次。这个需求用“高效正则”未必好写;即便用 Numpy 等技巧,也常需要整串扫描,而直觉算法在低层语言里会更快且更易读。
为了展示 Rust 的一些点,我们再加两种“重置计数”的变体:
- 遇到空白重置(即“某个单词内是否超过 X 次?”)
枚举(enum)
Rust 的枚举很强大。这里用一个“三选一”的简单枚举,表示
何时重置计数:
#[derive(Copy)]
enum Reset {
NewlinesReset,
SpacesReset,
NoReset,
}
结构体(struct)
结构体有点像 Python 的 dataclass,但能做的更复杂。
#[pyclass]
struct Counter {
what: char,
min_number: u64,
reset: Reset,
}
实现块(impl)
通过 impl
给结构体加方法。本例里方法再调用外部函数,方便拆分逻辑;复杂场景下编译器会内联,提升可读性同时不增加运行成本。
#[pymethods]
impl Counter {
#[new]
fn new(what: char, min_number: u64, reset: Reset) -> Self {
Counter{what: what, min_number: min_number, reset: reset}
}
fn has_count(
&self,
data: &str,
) -> bool {
has_count(self, data.chars())
}
}
函数
Rust 变量默认不可变;计数 current_count
需要变化,所以要用 mut
。
fn has_count(cntr: &Counter, chars: std::str::Chars) -> bool {
let mut current_count : u64 = 0;
for c in chars {
if got_count(cntr, c, &mut current_count) {
return true;
}
}
false
}
循环逐字符处理,并调用 got_count
。这也演示了可变引用的传递:调用方与被调方都要显式标注可变,修改意图更清晰。
计数逻辑
重置 → 自增 → 比较阈值。Rust 的语句序列以最后一个表达式的值为结果。
fn got_count(cntr: &Counter, c: char, current_count: &mut u64) -> bool {
maybe_reset(cntr, c, current_count);
maybe_incr(cntr, c, current_count);
*current_count >= cntr.min_number
}
重置
这里用到了模式匹配。完整讲解可以开一门课——本例只匹配元组的若干情形:
fn maybe_reset(cntr: &Counter, c: char, current_count: &mut u64) -> () {
match (c, cntr.reset) {
('\n', Reset::NewlinesReset) | (' ', Reset::SpacesReset)=> {
*current_count = 0;
}
_ => {}
};
}
自增
按需比较字符并累加:
fn maybe_incr(cntr: &Counter, c: char, current_count: &mut u64) -> (){
if c == cntr.what {
*current_count += 1;
};
}
注:为讲解直观,本文代码偏教学取向,并非最佳实践或完美 API 设计范式。
把 Rust 包给 Python 用
可以使用 PyO3。这个 Rust crate 通过注解把 Rust 类型/方法暴露为 Python 扩展,让两端更易同时迭代。
引用 PyO3
use pyo3::prelude::*;
包装枚举
派生 Clone/Copy
便于在 Python 侧使用与传递。
#[pyclass]
#[derive(Clone)]
#[derive(Copy)]
enum Reset {
/* ... */
}
包装结构体
用 #[pyclass]
生成必要接口。
#[pyclass]
struct Counter {
/* ... */
}
包装实现(构造器)
#[pymethods]
+
#[new]
指定 Python 侧的构造方法。
#[pymethods]
impl Counter {
#[new]
fn new(what: char, min_number: u64,
reset: Reset) -> Self {
Counter{what: what,
min_number: min_number, reset: reset}
}
/* ... */
}
定义模块
用 #[pymodule]
指定初始化函数与导出内容。
#[pymodule]
fn counter(_py: Python, m: &PyModule
) -> PyResult {
m.add_class::()?;
m.add_class::()?;
Ok(())
}
?
表示可能失败(如类未正确注册);PyResult
会在导入时转换成 Python 异常。
用 maturin 开发/构建
快速迭代:把编译后的扩展直接装到当前虚拟环境。
$ maturin develop
产出分发包:
$ maturin build
会生成 manylinux 的 wheel(按 CPU 架构区分),可上传到 PyPI。
在 Python 里使用
这一部分最“丝滑”:用法几乎与纯 Python 库别无二致。这也意味着:如果你在优化既有 Python 库,只要接口不变,原有单测就能直接覆盖到 Rust 实现。
导入
import counter
构造
我们暴露了构造器,因此可以直接在 Python 侧实例化(也可以设计成由其他函数返回)
cntr = counter.Counter(
'c',
3,
counter.Reset.NewlinesReset,
)
调用
检验字符串里是否至少有三个 'c'
:
>>> cntr.has_count("hello-c-c-c-goodbye")
True
加入换行(触发重置),不再满足“三个 c 连续出现”:
>>> cntr.has_count("hello-c-c-\nc-goodbye")
False
Rust + Python,其实很容易
Press enter or click to view image in full size
本文的目的,是让你相信把 Rust 与 Python 结合并不难。 Rust 负责
高性能与安全,但上手曲线更陡;Python 负责极快迭代,但存在性能上限。
因此:原型用 Python,瓶颈用 Rust。 有了 maturin,开发与发布都更顺畅:写 → 构建 → 享受组合拳。
全栈AI·探索:涵盖动效、React Hooks、Vue 技巧、LLM 应用、Python 脚本等专栏,案例驱动实战学习,点击二维码了解更多详情。
20个前端开发者必备的响应式布局