Skip to main content

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.