Rust 与 Python 混合编程
前言
类似 pydantic-core 和 ruff 这类的库都使用了 Python + Rust 开发的方式。Rust 可用于加速和扩展 Python 功能,本文提供了一些 Python 和 Rust 混合编程的示例。
1. IDE 配置
1.1 RustRover
可以在 RustRover 中安装插件 Python Community Edition 来提供对 Python 的支持。
1.2 VS Code
在 VS Code 中,安装 Python 和 Rust 对应的插件即可:
# Python 语言支持
code --install-extension ms-python.python
# Ruff 格式化
code --install-extension charliermarsh.ruff
# Rust 语言支持
code --install-extension rust-lang.rust-analyzer
# Rust 语法高亮
code --install-extension dustypomerleau.rust-syntax
# TOML 文件支持
code --install-extension tamasfe.even-better-toml
# 依赖检查
code --install-extension fill-labs.dependi
2. Python 调用 Rust
2.1 PyO3
Python 调用 Rust 代码是通过 PyO3 来实现的。
首先,创建一个全新的 Rust 项目,项目类型为库,将 pyo3
添加到依赖:
[package]
name = "rust-python-01"
version = "0.1.0"
edition = "2021"
[lib]
name = "rust_core"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.23.3", features = ["extension-module"] }
Cargo.toml
文件中,crate-type
设置为 cdylib
,表示生成动态链接库。
编写 src/lib.rs
文件:
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
/// Adds two numbers
/// # Arguments
/// * `x` - The first number
/// * `y` - The second number
#[pyfunction]
fn add(x: i32, y: i32) -> PyResult<i32> {
Ok(x + y)
}
#[pymodule]
fn rust_core(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(add, m)?)?;
Ok(())
}
这是一个简单的 Rust 函数,接收两个整数,返回它们的和。
将其编译为动态库文件:
cargo build --release
编译后得到 target/release/rust_core.dll
文件(Windows 下),然后将 rust_core.dll
文件复制到 Python 项目的目录下,重命名为 rust_core.pyd
。
编写测试代码:
import rust_core
def main():
print(rust_core.add(1, 2))
if __name__ == '__main__':
main()
运行测试代码,输出 3
,表示 Python 成功调用了 Rust 代码。
2.2 进阶示例
如果没有类型提示,则可以编写存根文件 rust_core.pyi
:
def add(x: int, y: int) -> int:
"""
Adds two numbers
:param x: The first number
:param y: The second number
:return: sum of the two numbers
"""
我们也可以输出 rust_core.add.__doc__
来查看函数的文档注释,这和 Rust 代码中的文档注释是一致的。
下面,我们在 src/lib.rs
添加一个函数,计算矩阵中所有数字的和:
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
/// Adds two numbers
/// # Arguments
/// * `x` - The first number
/// * `y` - The second number
#[pyfunction]
fn add(x: i32, y: i32) -> PyResult<i32> {
Ok(x + y)
}
/// Sums all the elements in a matrix//
/// # Arguments//
/// * `matrix` - A 2D matrix//
#[pyfunction]
fn sum_matrix(matrix: Vec<Vec<i32>>) -> PyResult<i32> {
let mut sum = 0;
for row in matrix {
for val in row {
sum += val;
}
}
Ok(sum)
}
#[pymodule]
fn rust_core(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(add, m)?)?;
m.add_function(wrap_pyfunction!(sum_matrix, m)?)?;
Ok(())
}
在 rust_core.pyi
中添加存根:
def sum_matrix(matrix: List[List[int]]) -> int:
"""
Sums all the elements in a matrix
:param matrix: The matrix
:return: sum of all elements
"""
测试代码:
import rust_core
def main():
print(rust_core.sum_matrix([[1, 2], [3, 4]]))
if __name__ == "__main__":
main()
2.3 传递哈希表
同样的,我们编写一个函数,将邻接矩阵转换为邻接表:
/// Converts adjacency matrix to adjacency map
/// # Arguments
/// * `matrix` - A 2D matrix
#[pyfunction]
fn matrix_to_map(matrix: Vec<Vec<i32>>) -> PyResult<HashMap<i32, HashSet<i32>>> {
let mut hashmap = HashMap::new();
for (i, row) in matrix.iter().enumerate() {
for (j, &value) in row.iter().enumerate() {
if value != 0 {
hashmap.entry(i as i32)
.or_insert_with(HashSet::new)
.insert(j as i32);
}
}
}
Ok(hashmap)
}
然后可以使用 Python 调用这个函数:
import rust_core
def main():
print(rust_core.matrix_to_map([[0, 1, 0], [1, 0, 1], [0, 1, 0]]))
if __name__ == "__main__":
main()
3. 使用 Maturin
3.1 创建项目
Maturin 是使用 pyo3
、cffi
和 uniffi
绑定以及 Rust 二进制文件构建和发布 Crates 作为 Python 包。
我们可以使用 maturin
来构建 Python 包,首先我们可以全局安装 maturin
:
uv tool install maturin
我们创建一个全新的 Python + Rust 项目:
maturin new rust-python-02
然后选择 pyo3
类型,接着我们创建虚拟环境:
uv venv
下面进入虚拟环境即可,可以先安装项目:
uv sync
下面新建 main.py
文件进行测试:
import rust_python_02
def main():
print(rust_python_02.sum_as_string(1, 2))
if __name__ == "__main__":
main()
输出 "3"
代表成功。
如果我们希望改动 Rust 代码并将其重新安装到 Python 环境中,可以使用 maturin develop
,如果你不使用 uv
,可以去掉 --uv
参数:
maturin develop --uv
这样就可以在 Python 中调用最新的 Rust 代码了。
3.2 在 Rust 中使用 Python
我们有如下 Python 代码,用于生成一个 UUID:
import uuid
uuid_obj = uuid.uuid4()
print("uuid:", uuid_obj)
如果我们希望在 Rust 中调用 Python 模块来生成一个 UUID,可以使用 PyO3 提供的 Python
模块。
下面,创建一个 src/main.rs
文件:
use pyo3::prelude::*;
fn main() {
let res: Result<String, PyErr> = Python::with_gil(|py| {
let uuid = py.import("uuid")?;
let uuid_obj = uuid.call_method0("uuid4")?;
Ok(uuid_obj.str()?.to_string())
});
match res {
Ok(uuid) => println!("uuid: {}", uuid),
Err(e) => println!("Error: {}", e),
}
}
然后将编译目标添加到 Cargo.toml
文件:
[[bin]]
name = "rust_python_test"
path = "src/main.rs"
编译并运行:
cargo run
可以看到输出一个 UUID。