Cloud Parts

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.

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 from Cargo.toml and pyproject.toml
  • A short edit loop: maturin develop rebuilds 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.py package that can’t fit them are pointed to setuptools-rust
  • maturin develop is quicker but doesn’t support every feature that maturin build followed by pip install does
  • 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 upload and publish commands are deprecated since 1.11, with upload due to be removed in 2.0; the README recommends uv publish instead

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.whl

What maturin can package#

It detects the kind from Cargo.toml, or you pass --bindings (or bindings under [tool.maturin]).

pyo3

default for extension modules

A 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 version

A 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 version

Mozilla’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 PyPI

A 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.py describing 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 install compile a Rust package like any other, from an sdist that contains the same files as cargo 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 linux tag, without a separate step.
Ship Rust binaries through PyPI too
Packaging binaries arrived early, in 0.3. A bin wheel puts a Rust executable on PATH through pip, which is how Python users can install Rust command-line tools without Cargo.

Timeline

  1. 2018

    pyo3-pack 0.1.0 released on 22 August; CFFI and develop added in September

  2. 2019

    0.7.0 on 30 August renames the project maturin and adds PEP 517 support

  3. 2022

    zig cc linking (0.12.7, February) and UniFFI bindings (0.14, November)

  4. 2023

    maturin 1.0.0 on 23 May

  5. 2026

    1.11 (January) deprecates upload and publish; 1.14 (June) builds abi3t wheels for PyO3 0.29

Sources9 · facts checked 9 October 2026

Improve this page on GitHub