maturin
Build and publish Python wheels that bundle Rust extensions with maturin.
Recipe
# pyproject.toml
[build-system]
requires = ["maturin>=1.8"]
build-backend = "maturin"
[project]
name = "myrust"
requires-python = ">=3.10"maturin develop --releaseWhen to reach for this: Shipping PyO3 extensions to PyPI or internal package indexes with standard Python tooling.
Working Example
# Cargo.toml
[package]
name = "myrust"
version = "0.1.0"
edition = "2024"
[lib]
name = "myrust"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.23", features = ["extension-module"] }# pyproject.toml
[tool.maturin]
module-name = "myrust"
python-source = "python"
features = ["pyo3/extension-module"]python/
myrust/
__init__.py # re-exports
maturin build --release --out dist
pip install dist/myrust-0.1.0-*.whlWhat this demonstrates:
cdylibcrate type produces the native moduleextension-modulefeature tells PyO3 it is loaded by Pythonmaturin developinstalls into active venv for fast iterationmaturin buildemits platform-specific wheels indist/
Deep Dive
Commands
| Command | Use |
|---|---|
maturin develop | Local editable install |
maturin build | Produce wheels for CI |
maturin publish | Upload to PyPI |
maturin generate-ci | GitHub Actions template |
abi3 Wheels
[dependencies]
pyo3 = { version = "0.23", features = ["extension-module", "abi3-py39"] }One wheel per platform covers Python 3.9+.
Mixed Python + Rust Layout
my-package/
Cargo.toml
pyproject.toml
python/mypkg/__init__.py
src/lib.rs
Gotchas
- Missing venv - Installs to wrong Python. Fix: Activate venv before
maturin develop. - Manylinux compliance - Linux wheels must build on compatible base images. Fix: Use
maturin build --compatibility manylinux_2_28or CI docker images. - macOS universal2 - Apple Silicon + Intel need fat wheels. Fix:
maturin build --target universal2-apple-darwin. - SDist without Rust - Source tarball users need Rust toolchain. Fix: Publish wheels for all supported platforms.
- License files - PyPI requires metadata. Fix: Set
licenseinpyproject.tomland include LICENSE in manifest.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
setuptools-rust | Legacy setuptools workflows | New PyO3 projects |
cffi + prebuilt dll | Users cannot compile Rust | You control all platforms |
uv build with maturin backend | Modern Python packaging | N/A for Rust extensions |
FAQs
maturin vs pip install .?
maturin understands Rust layout and PyO3 features. Plain pip may miss extension build steps.
Poetry compatibility?
Use maturin as PEP 517 backend. Poetry manages deps; maturin builds the extension.
uv and maturin?
uv pip install works on maturin-built wheels. uv sync in hybrid repos needs documented develop workflow.
Private PyPI?
maturin publish --repository-url https://internal/simple/.
Depend on other Rust crates?
Normal Cargo dependencies. maturin runs cargo build under the hood.
Include pure Python stubs?
Put .pyi files in python/ package for type checkers.
Cross-compile Windows from Linux?
Use cross toolchains and maturin target flags in CI matrix.
Debug builds?
maturin develop without --release for faster builds; slower runtime.
Workspace Cargo.toml?
maturin supports workspaces with manifest-path pointing to the extension crate.
Audit wheels?
pip audit and cargo deny for dual-language supply chain checks.
Related
- PyO3 - extension code
- Interop Basics - Python strategy
- Data Marshalling - Python buffer types
- Interop Best Practices - release hygiene
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.