Rust and Python are a match made in heaven:
- Python is flexible and has an amazing ecosystem.
- Rust is fast, safe, and low-level.
What if you could use Rust’s performance directly inside your Python programs? That’s exactly what PyO3 lets you do.
In this guide, we’ll build a Python extension in Rust that processes input events, translates text, and even converts TOML to JSON—all callable from Python.
⚙️ Step 1: Project Setup
First, create a Rust library project:
cargo new --lib afrim_py
cd afrim_py
Update Cargo.toml
:
[lib]
name = "afrim_py"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.21", features = ["extension-module"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
pythonize = "0.21"
Install maturin for packaging:
pip install maturin
🦀 Step 2: Writing Rust Functions for Python
Let’s start with a simple Rust function exposed to Python.
use pyo3::prelude::*;
use toml;
/// Convert TOML to JSON and return it as a Python string.
#[pyfunction]
fn convert_toml_to_json(content: &str) -> PyResult<String> {
let data: toml::Value = toml::from_str(content)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;
Ok(serde_json::to_string(&data)?)
}
In lib.rs
, register it as part of a Python module:
#[pymodule]
fn afrim_py(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(convert_toml_to_json, m)?)?;
Ok(())
}
🧩 Step 3: Exposing Rust Structs as Python Classes
You can also expose Rust structs as Python classes. For example, here’s a Preprocessor
with methods:
#[pyclass]
pub struct Preprocessor {
buffer: String,
}
#[pymethods]
impl Preprocessor {
#[new]
fn new() -> Self {
Self { buffer: String::new() }
}
fn process(&mut self, input: &str) {
self.buffer.push_str(input);
}
fn get_buffer(&self) -> String {
self.buffer.clone()
}
}
Now Python can do:
from afrim_py import Preprocessor
p = Preprocessor()
p.process("Hello")
print(p.get_buffer()) # "Hello"
🚀 Step 4: Building and Installing
Run:
maturin develop
This builds your Rust code into a Python extension and installs it locally.
🐍 Step 5: Using It in Python
Here’s a taste of what you can now do:
from afrim_py import convert_toml_to_json, Preprocessor
print(convert_toml_to_json("[info]\nname = 'sample'"))
# {"info": {"name": "sample"}}
p = Preprocessor()
p.process("Rust ❤️ Python")
print(p.get_buffer())
# "Rust ❤️ Python"
🎯 Why This Matters
- Performance: Rust handles heavy tasks much faster than pure Python.
- Safety: No segfaults or data races.
- Integration: You can extend existing Python libraries instead of rewriting them.
This approach is perfect for:
- Accelerating parsing, preprocessing, or machine learning pipelines.
- Reusing Rust libraries in Python apps.
- Building new Python modules powered by Rust.
✅ Final Thoughts
With PyO3 and maturin, bridging Rust and Python is easier than ever. You write safe, fast Rust code—and expose it directly as Python modules.
If you’re building performance-critical Python tools, give this combo a try. It’s like giving Python a turbo engine without losing its simplicity.
Happy hacking with Rust & Python! 🦀🐍