
maturin
The build tool that turns a Rust crate with PyO3, CFFI or UniFFI bindings, or a plain Rust binary, into Python wheels, installs it into a virtualenv while you develop, and serves as a PEP 517 build backend.
- What it is
- Foreign function interfaces
- Part of
- Tool for PyO3
- Type
- Open source
- Made by
- PyO3 project (konstin, messense and contributors)
- License
- Apache 2.0 or MIT, at your option
- Facts checked
- 9 October 2026
What it is
maturin reads Cargo.toml and pyproject.toml, runs Cargo, and packages the result as a wheel tagged for the right interpreter, OS and CPU. It picks the binding style from the crate: pyo3 for extension modules, cffi (with a C header generated by cbindgen), uniffi, which generates ctypes bindings from an interface definition, or bin, which installs a Rust executable as a script on the user’s PATH. The distribution name comes from [package] in Cargo.toml, the import name from [lib], and metadata in pyproject.toml takes precedence over Cargo’s. Three commands cover the loop: maturin new scaffolds a project, maturin develop compiles a debug build and installs it into the active virtualenv, and maturin build --release writes wheels to target/wheels. Because it is also a PEP 517 backend (build-backend = "maturin"), pip install . builds the same way, and a mixed layout keeps Python sources and the compiled module in one package.
Most of its work is portability. A Linux wheel may only link a small set of system libraries, the manylinux rules, so maturin checks the built library with its own reimplementation of auditwheel and tags the wheel to match. For compliant wheels you build in its manylinux2014-based Docker image or pass --zig, which links with zig cc for the chosen manylinux version; Rust itself needs glibc 2.17, so manylinux2014 is the oldest usable target. It cross-compiles to Windows MSVC through cargo-xwin, builds abi3 and abi3t stable-ABI wheels with PyO3, and maturin generate-ci github writes a GitHub Actions workflow that builds wheels for Linux, musllinux, Windows and macOS plus a source distribution. maturin 1.15 builds for Python 3.8 and newer on Windows, Linux, macOS and FreeBSD. orjson, Polars, pydantic-core and Ruff are among the projects built with it.
Why teams pick it
- From a Cargo crate to an installable wheel with no
setup.py: package metadata comes fromCargo.tomlandpyproject.toml - A short edit loop:
maturin developrebuilds into the current virtualenv, and PEP 660 editable installs work with mixed Rust and Python projects - Portable Linux wheels out of the box: built-in manylinux checks, a ready Docker image and cross-linking with zig
- One tool for PyO3, CFFI and UniFFI bindings and for shipping plain Rust command-line tools through PyPI
Watch out for
- Built around Cargo and its own conventions; projects adding Rust to an existing
setup.pypackage that can’t fit them are pointed to setuptools-rust maturin developis quicker but doesn’t support every feature thatmaturin buildfollowed bypip installdoes- Builds through the PEP 517 backend are always release builds, so
pip install .from source pays for a full optimised compile - Wheels are per platform: users on a platform without one fall back to the source distribution and need a Rust toolchain
- Its own
uploadandpublishcommands are deprecated since 1.11, withuploaddue to be removed in 2.0; the README recommendsuv publishinstead
How it fits with other technologies
From nothing to an importable PyO3 module and a wheel (maturin 1.15, PyO3 0.29, Python 3.14 on an Apple-silicon Mac) · bash
python3 -m venv .venv && source .venv/bin/activate
pip install maturin
maturin new --bindings pyo3 fastcsv && cd fastcsv
# creates Cargo.toml, pyproject.toml, src/lib.rs and .github/workflows/CI.yml
cat > src/lib.rs <<'EOF'
use pyo3::prelude::*;
#[pymodule]
mod fastcsv {
use pyo3::prelude::*;
/// Split one line on commas and trim each field (no quoting rules).
#[pyfunction]
fn split_line(line: &str) -> Vec<String> {
line.split(',').map(|f| f.trim().to_string()).collect()
}
}
EOF
maturin develop # debug build, installed into .venv as an editable package
python -c "import fastcsv; print(fastcsv.split_line('a, b ,c'))"
# ['a', 'b', 'c']
maturin build --release # an optimised wheel for this interpreter and platform
ls target/wheels
# fastcsv-0.1.0-cp314-cp314-macosx_11_0_arm64.whlWhat maturin can package#
It detects the kind from Cargo.toml, or you pass --bindings (or bindings under [tool.maturin]).
pyo3
default for extension modulesA CPython, PyPy or GraalPy extension module. Detected when pyo3 is a dependency. With PyO3’s abi3 or abi3t features one wheel covers many Python versions; otherwise each wheel targets one version, such as cp314.
cffi
any Python versionA C-ABI library plus CFFI glue, compatible with every Python version including PyPy. maturin generates the C header with cbindgen and installs cffi into the virtualenv if it’s missing.
uniffi
any Python versionMozilla’s UniFFI generates ctypes bindings from an interface definition, so the same Rust library can also be bound to other languages.
bin
Rust tools on PyPIA Rust executable packaged into a wheel as a script, on the user’s PATH once installed. Detected when the crate has only a binary target and no pyo3 dependency or cdylib.
Getting wheels to users#
maturin build --release builds for the machine it runs on. For Linux wheels that PyPI accepts, build inside the ghcr.io/pyo3/maturin image or with --zig, and pass --compatibility pypi to reject targets PyPI won’t take. Windows MSVC targets can be cross-compiled through cargo-xwin, whose use means accepting Microsoft’s license.
Most projects let CI do this. maturin generate-ci github > .github/workflows/CI.yml writes a workflow that uses the PyO3/maturin-action action to build wheels across Linux, musllinux, Windows and macOS runners, builds an sdist, attests the artifacts and, on a tag, uploads everything with uv publish using an API token. The guide also shows how to switch it to PyPI trusted publishing.
See it in a real stack
No architecture uses maturin yet. Drop it into the playground and wire it up yourself.Open playground →Where it came from2018–2026
Packaging Rust for Python first meant setuptools-rust, a setuptools plugin configured from setup.py. On 22 August 2018 konstin released pyo3-pack 0.1.0, which described itself as “a zero configuration replacement for setuptools-rust”: it ran directly on a crate with no extra files, built wheels for Python 2.7 and 3.5+ on Windows, Linux and macOS, and could upload them to PyPI. Version 0.2 in September 2018 added CFFI bindings and the develop command.
With 0.7 on 30 August 2019 the project was renamed maturin, and the same release added PEP 517 support and the mixed Rust and Python layout, so pip could build a Rust package from source like any other. The releases after that went into portability and workflow: PEP 660 editable installs in 0.12.0 (November 2021), zig cc as a linker for cross-compiling and manylinux compliance in 0.12.7 (February 2022), and UniFFI bindings in 0.14 (November 2022). 1.0 followed on 23 May 2023.
Packaging around it kept moving. The generated CI workflow now publishes with uv publish, and maturin deprecated its own upload commands in 1.11 (January 2026), with upload slated for removal in 2.0. konstin and messense are listed as its authors.
Why it’s built this way
- Zero configuration on top of Cargo
- setuptools-rust needed a
setup.pydescribing the Rust extension. pyo3-pack ran on the crate itself, taking the package name from[package]and the module name from[lib], with no extra files to write. - Be a PEP 517 build backend, not only a CLI
- When pip finds no matching wheel it builds from the source distribution. As a backend, maturin lets
pip installcompile a Rust package like any other, from an sdist that contains the same files ascargo package. - Check manylinux compliance itself
- Linux wheels that link libraries outside the manylinux set fail on other distributions. maturin reimplements auditwheel’s check, so it can give each wheel the right platform tag, or fall back to a plain
linuxtag, without a separate step. - Ship Rust binaries through PyPI too
- Packaging binaries arrived early, in 0.3. A
binwheel puts a Rust executable onPATHthrough pip, which is how Python users can install Rust command-line tools without Cargo.
Timeline
- 2018
pyo3-pack 0.1.0 released on 22 August; CFFI and
developadded in September - 2019
0.7.0 on 30 August renames the project maturin and adds PEP 517 support
- 2022
zig cclinking (0.12.7, February) and UniFFI bindings (0.14, November) - 2023
maturin 1.0.0 on 23 May
- 2026
1.11 (January) deprecates
uploadandpublish; 1.14 (June) buildsabi3twheels for PyO3 0.29
- GitHub: pyo3-pack v0.1.0 Readme (“zero configuration replacement for setuptools-rust”)
- maturin Changelog (0.1.0 August 2018, 0.2.0, 0.7.0 rename, 0.12.0, 0.12.7, 0.14.0, 1.0.0, 1.11.0, 1.14.0 abi3t)
- GitHub: maturin releases (1.11.1 January 2026: upload deprecated, removed in 2.0; 1.14.0 June 2026)
- GitHub: maturin issue #2334, Deprecate the upload command in favor of twine/uv
- maturin Cargo.toml (authors konstin and messense)
Sources9 · facts checked 9 October 2026
- GitHub: maturin README (commands, naming, mixed layout, PEP 517, manylinux, examples, license)
- maturin User Guide: Bindings (pyo3, abi3 and abi3t, cffi with cbindgen, uniffi, bin)
- maturin User Guide: Local development (maturin develop, PEP 660 editable installs)
- maturin User Guide: Distribution (manylinux, zig, cargo-xwin, generate-ci, trusted publishing)
- maturin User Guide: Tutorial
- maturin User Guide: Project layout
- PyO3 user guide 0.29.3: Building and distribution (maturin versus setuptools-rust)
- GitHub: PyO3 README (getting started with maturin new and maturin develop)
- GitHub: maturin releases (1.15.0, August 2026)