Building Rust Extensions for Python
Episode #487,
published Sun, Dec 1, 2024, recorded Thu, Nov 21, 2024
There has been a lot of changes in the low-level Python space these days. The biggest has to be how many projects have rewritten core performance-intensive sections in Rust. Or even the wholesale adoption of Rust for newer projects such as uv and ruff. On this episode, we dive into the tools and workflow needed to build these portions of Python apps in Rust with David Seddon and Samuel Colvin.
Episode Deep Dive
1. Motivation for Using Rust along with Python
- Python’s Performance Boundaries Python is fantastic as a high-level, productive language. But certain parts of an application—especially performance-critical code—can become slow in pure Python.
- Rust’s Advantages over C for Extensions
While CPython historically supports C extensions, Rust provides:
- A modern compiler toolchain and package manager (Cargo).
- Memory safety at compile time via its “borrow checker,” drastically reducing entire classes of bugs common in C.
- A strong ecosystem with many high-quality libraries for tasks like JSON parsing, web servers, etc.
2. The Tooling: PyO3, Maturin, and Cargo
- PyO3
A Rust library that simplifies creating Python extension modules. It wraps and exposes Rust functions, classes, and logic to Python.
Example benefits:
- Reduces manual handling of low-level CPython APIs.
- Automatically manages ref-counts for Python objects in Rust.
- Cargo
Rust’s package manager and build system. You specify Rust crates (similar to PyPI packages) in your
Cargo.toml
, and Cargo handles compilation, dependency resolution, and more. - Maturin
A Python package (installable via
pip install maturin
) that integrates Cargo with Python packaging conventions. Maturin lets you:- Build a wheel (
.whl
) for your Rust-based extension modules. - Automate creation of many-platform wheels.
- Use commands like
maturin develop
to compile your Rust code and install it into your active Python environment for testing.
- Build a wheel (
3. Real-World Success Stories
- Pydantic Core
- Samuel Colvin rewrote Pydantic’s core internals from Python to Rust, greatly boosting performance and reliability.
- The Rust-based internals rely heavily on PyO3 + Maturin for packaging.
- “jiter” library (for fast JSON parsing) also came out of these efforts, allowing Pydantic to parse JSON more quickly than Serde in many use cases.
- Jiter is used by other major Python projects (e.g. the official OpenAI Python SDK) to parse JSON at scale.
- Import Linter / Grimp
- David Seddon built “import linter” for architectural rules in a massive Django monolith (>8 million lines of code, ~400 PR authors per week).
- Import linter uses a sub-library named Grimp for dependency graph building.
- Originally written purely in Python, but crucial parts of its dependency-graph algorithms in Grimp were ported to Rust with PyO3, giving ~6x performance boosts on certain tasks.
- Also replaced a pure-Python Fluent translator for app localization with a Rust-based Fluent library, cutting startup time from minutes to ~30s.
- Other Notable Rust Wrappers
- Granian (wrapping Rust HTTP logic, e.g. Hyper).
- rloop for async event loops.
- rtoml for TOML parsing, etc.
4. Practical Steps & Workflow
- Install Rust
- Use RustUp (the Rust toolchain installer/manager).
- You’ll now have
cargo
andrustc
available.
- Create or Add Rust Crate
cargo new
ormaturin new
to scaffold a project structure containing aCargo.toml
.- Add
[dependencies]
likepyo3 = "..."
or other crates inCargo.toml
.
- Python Integration
- Install Maturin via
pip install maturin
. maturin develop
builds and installs the Rust extension into your local Python environment for interactive testing.- (Optional) Provide
.pyi
stubs so type checkers (mypy, pyright, etc.) know signatures of your Rust-implemented Python functions.
- Install Maturin via
- Publishing
- Maturin can also build multi-platform wheels and upload them to PyPI (e.g.
maturin build && maturin upload
). - Advanced workflows generate CI config to build 50-60 wheels across CPU architectures, OSes, and Python versions, then push them to PyPI.
- Maturin can also build multi-platform wheels and upload them to PyPI (e.g.
5. Advice & Final Thoughts
- Transitioning to Rust
- Don’t be discouraged if you feel “lost” at first. Rust’s strictness (the borrow checker) can feel daunting, but it largely prevents memory errors at compile time.
- Writing smaller Rust “subsystems” can already yield big performance gains—no need to rewrite an entire codebase.
- Smoother Developer Workflow
- Combine Rust’s speed & concurrency with Python’s clarity & ease of iteration.
- Keep the majority of your app in Python; only “Rustify” performance bottlenecks.
- Rust can wrap advanced libraries from crates.io that significantly outperform pure Python (e.g., specialized parsing, HPC tasks).
- Rust Ecosystem
- Has a reputation for thoroughness and well-tested libraries, though like any open-source domain, some crates are abandoned. Evaluate each library’s activity level before adoption.
- Impact on Architecture
- Tools like import linter (Grimp) and Rust-based functionality keep giant monoliths manageable (enforcing architecture boundaries, speeding up critical code paths).
Overall Takeaway With a handful of readily available tools (PyO3, Maturin, Cargo), Python developers can confidently tap into Rust’s performance and memory safety. Both David’s and Samuel’s experiences show that even partial migrations (e.g., rewriting only the critical hotspots in Rust) lead to notable wins in speed, maintainability, and system stability.
Links from the show
Samuel Colvin: github.com/samuelcolvin
David Seddon: github.com/seddonym
David's blog: seddonym.me
Pydantic: pydantic.dev
PEP 0759: peps.python.org
TypeShed: github.com
Maturin: maturin.rs
rloop: github.com
Install Rust: rust-lang.org
Py03: pyo3.rs
The Rust Programming Language (book): https://doc.rust-lang.org/book/
Grimp: github.com
Grimp Workflows: github.com
White House recommends memory safe languages: whitehouse.gov
Installing Rust: rust-lang.org
jiter: github.com
import-linter: github.com
Logfire: pydantic.dev
Crabs in Snakes, David Seddon, Pycon Italia: youtube.com
Kraken engineering blog: engineering.kraken.tech
Serde: serde.rs
Mypy stub testing: mypy.readthedocs.io
Watch this episode on YouTube: youtube.com
Episode transcripts: talkpython.fm
--- Stay in touch with us ---
Subscribe to Talk Python on YouTube: youtube.com
Talk Python on Bluesky: @talkpython.fm at bsky.app
Talk Python on Mastodon: talkpython
Michael on Bluesky: @mkennedy.codes at bsky.app
Michael on Mastodon: mkennedy
David Seddon: github.com/seddonym
David's blog: seddonym.me
Pydantic: pydantic.dev
PEP 0759: peps.python.org
TypeShed: github.com
Maturin: maturin.rs
rloop: github.com
Install Rust: rust-lang.org
Py03: pyo3.rs
The Rust Programming Language (book): https://doc.rust-lang.org/book/
Grimp: github.com
Grimp Workflows: github.com
White House recommends memory safe languages: whitehouse.gov
Installing Rust: rust-lang.org
jiter: github.com
import-linter: github.com
Logfire: pydantic.dev
Crabs in Snakes, David Seddon, Pycon Italia: youtube.com
Kraken engineering blog: engineering.kraken.tech
Serde: serde.rs
Mypy stub testing: mypy.readthedocs.io
Watch this episode on YouTube: youtube.com
Episode transcripts: talkpython.fm
--- Stay in touch with us ---
Subscribe to Talk Python on YouTube: youtube.com
Talk Python on Bluesky: @talkpython.fm at bsky.app
Talk Python on Mastodon: talkpython
Michael on Bluesky: @mkennedy.codes at bsky.app
Michael on Mastodon: mkennedy