Calling Rust from Python: A Practical FFI Walkthrough
1 min read
The hot path
A data processing pipeline was spending 60% of its time in a string parsing loop. Pure Python, no way around it.
The Rust side
use pyo3::prelude::*;
#[pyfunction]
fn parse_log_line(line: &str) -> PyResult<LogEntry> {
let parts: Vec<&str> = line.splitn(5, ' ').collect();
// ... parse into struct
}
#[pymodule]
fn parser(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(parse_log_line, m)?)?;
Ok(())
}
The Python side
from parser import parse_log_line
# Same API, 40x faster
for line in log_file:
entry = parse_log_line(line)
Build setup
# Cargo.toml — single file, no build script needed
[lib]
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.22", features = ["extension-module"] }
What I learned
PyO3 makes Rust-from-Python feel native. The biggest win wasn’t even the speed — it was the type safety. Rust’s compiler caught edge cases (null bytes, malformed timestamps) that Python had been silently swallowing.