Fix: Maturin Not Working — develop Errors, ABI3 Wheels, manylinux, and macOS Universal Builds
Quick Answer
How to fix Maturin errors — maturin develop fails outside venv, abi3 forward compatibility, manylinux wheel auditing, macOS universal2 cross-compile, pyproject.toml vs Cargo.toml conflicts, and PyO3 feature flags.
The Error
You run maturin develop in your project and get this:
$ maturin develop
💥 maturin failed
Caused by: You need to be inside a virtualenv or conda environment to use developOr maturin build succeeds, you upload to PyPI, and users on Linux can’t install:
ERROR: Could not find a version that satisfies the requirement my_pkg
ERROR: No matching distribution found for my_pkgOr PyO3 refuses to compile against the Python you have:
error: the configured Python interpreter version (3.13) is newer than PyO3's maximum supported version (3.12)
help: please check if an updated version of PyO3 is availableOr your macOS wheel works on the build machine but fails on a different Mac:
ImportError: dlopen(...): symbol not found in flat namespace '_PyObject_...'Why This Happens
Maturin orchestrates three things at once: the Rust build (via Cargo), the Python wheel format, and the platform’s ABI rules. Most failures map to one of these layers:
- Wrong runtime for
develop.maturin developbuilds and installs into a Python environment. It refuses to install into the system Python because that path leads to permission errors and breaks system packages. You need an active venv or conda env. - Platform-specific wheels that aren’t tagged correctly. A wheel built on Ubuntu 24.04 isn’t installable on Ubuntu 20.04 (different glibc), and a wheel built on macOS 14 isn’t installable on macOS 11. The fix is
manylinuxandmacosx_x_ytags, which Maturin handles when you ask for them. - PyO3 version vs Python version mismatch. PyO3 has a maximum Python version it knows about. Build a wheel against Python 3.13 with an old PyO3, and it refuses. Upgrade PyO3, or use the
abi3feature for forward compatibility. pyproject.tomlandCargo.tomldisagree. Maturin reads both. The package name in[project]must match the cdylib name in Cargo or your import fails after install.
Fix 1: Activate a Venv Before develop
maturin develop writes the compiled .so / .pyd into whichever Python environment is active. It needs one:
python -m venv .venv
source .venv/bin/activate # Linux/macOS
.venv\Scripts\activate # Windows PowerShell
pip install maturin
maturin developFor uv users:
uv venv
source .venv/bin/activate
uv pip install maturin
maturin developIf you’re using a global tool like pipx install maturin, that’s fine for the binary — but you still need a venv where the built module gets installed. maturin develop always installs into the active Python interpreter, not into the env that owns the maturin binary.
Pro Tip: maturin develop --release builds with optimizations on. Default is debug mode, which can be 10-100x slower at runtime. Always benchmark with --release.
Fix 2: Use abi3 for Forward-Compatible Wheels
Without abi3, you need to build a separate wheel for every Python version (3.10, 3.11, 3.12, 3.13). With abi3, one wheel works across many versions — at the cost of using only the stable Python C API.
In Cargo.toml:
[lib]
name = "my_pkg"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.22", features = ["extension-module", "abi3-py310"] }abi3-py310 means “minimum Python 3.10, works on every Python ≥ 3.10.” Maturin picks up the feature flag and produces a wheel like my_pkg-0.1.0-cp310-abi3-linux_x86_64.whl instead of cp312-cp312-linux_x86_64.whl.
Trade-off: abi3 disallows some PyO3 features that need the unstable API (a few macros, certain protocol implementations). The PyO3 docs list what’s missing — for most numeric/data libraries it’s fine.
Fix 3: Build manylinux Wheels for Linux
A wheel built directly on your Ubuntu workstation usually isn’t installable elsewhere because it links against your specific glibc. You need a manylinux wheel, built inside a special container:
docker run --rm -v $(pwd):/io ghcr.io/pyo3/maturin build --releaseOr use the --zig flag to cross-compile without Docker (faster on macOS/Windows hosts):
maturin build --release --target x86_64-unknown-linux-gnu --zigVerify the wheel’s manylinux tag is correct:
unzip -p target/wheels/*.whl my_pkg-0.1.0.dist-info/WHEEL | grep Tag
# Tag: cp310-abi3-manylinux_2_17_x86_64manylinux_2_17 means “works on any Linux with glibc 2.17+” (which is essentially every Linux distro from 2014 onward).
Common Mistake: Tagging a wheel as manylinux2014 when it actually links against a newer glibc. Use auditwheel to check (and repair, if possible):
auditwheel show target/wheels/my_pkg-0.1.0-cp310-abi3-linux_x86_64.whl
auditwheel repair target/wheels/my_pkg-0.1.0-cp310-abi3-linux_x86_64.whlThe Maturin Docker image runs auditwheel automatically.
Fix 4: macOS Universal2 (Apple Silicon + Intel)
For wheels that run on both Apple Silicon and Intel Macs, build a universal2 wheel:
maturin build --release --target universal2-apple-darwinThis requires the Rust targets installed:
rustup target add aarch64-apple-darwin x86_64-apple-darwinTwo issues that bite:
- macOS deployment target. Set
MACOSX_DEPLOYMENT_TARGET=11.0(or whatever minimum you want to support) before building. Without it, your wheel only works on the macOS version you built on. - Dependencies in Cargo.toml that need C libs. If your crate depends on, say,
openssl-sys, the universal2 build needs both arches of OpenSSL available. Userustlsinstead when possible, or usevendoredfeatures.
MACOSX_DEPLOYMENT_TARGET=11.0 maturin build --release --target universal2-apple-darwinFix 5: Align pyproject.toml and Cargo.toml
Maturin reads metadata from both files. The package name must agree:
# pyproject.toml
[project]
name = "my_pkg"
version = "0.1.0"
requires-python = ">=3.10"
[build-system]
requires = ["maturin>=1.7,<2.0"]
build-backend = "maturin"
[tool.maturin]
features = ["pyo3/extension-module"]# Cargo.toml
[package]
name = "my_pkg"
version = "0.1.0"
edition = "2021"
[lib]
name = "my_pkg"
crate-type = ["cdylib"]Three rules:
[project].nameand[lib].namemust match — that’s the import name.[project].versionand[package].versionshould match — Maturin won’t error if they differ but PyPI shows whichever you tag.crate-type = ["cdylib"]is required. Without it Cargo produces an executable, not a Python-loadable shared library.
Note: For a pure-Rust extension (no Python source), the layout is src/lib.rs. For mixed Rust + Python, put Python code in python/my_pkg/__init__.py and set python-source = "python" under [tool.maturin].
Fix 6: Upgrade PyO3 for Newer Python Versions
When PyO3 fails on a new Python release:
error: the configured Python interpreter version (3.13) is newer than PyO3's maximum supported version (3.12)Check PyO3’s release notes for the version that added support, and update Cargo.toml:
[dependencies]
pyo3 = { version = "0.22.4", features = ["extension-module", "abi3-py310"] }For a temporary unblock while waiting for a new PyO3 release, set:
export PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1
maturin build --releaseThis tells PyO3 to compile against the stable ABI and trust that future Python versions are compatible. It works for abi3 builds where you’ve stayed within the stable API.
Fix 7: Wheel Imports But Functions Are Missing
You install your wheel, import my_pkg works, but my_pkg.some_function() raises AttributeError. Two common causes:
The Rust function isn’t #[pyfunction] and isn’t registered. PyO3 only exposes what you tell it to:
use pyo3::prelude::*;
#[pyfunction]
fn add(a: i64, b: i64) -> i64 {
a + b
}
#[pymodule]
fn my_pkg(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(add, m)?)?;
Ok(())
}The #[pymodule] function name must match the package name (my_pkg here). Mismatched names → “module not found” or partial imports.
You have a Python __init__.py shadowing the Rust module. With a mixed layout, the import resolution is: Python finds python/my_pkg/__init__.py first, and it must re-export from the Rust extension:
# python/my_pkg/__init__.py
from .my_pkg import *
from .my_pkg import addMaturin builds the Rust extension as my_pkg.my_pkg in this layout — so __init__.py has to bridge it.
Fix 8: Publishing to PyPI
Once your wheels build cleanly, publish:
maturin publish --username __token__ --password $PYPI_TOKENOr in two steps — build then upload with twine:
maturin build --release --target x86_64-unknown-linux-gnu --zig
maturin build --release --target aarch64-unknown-linux-gnu --zig
maturin build --release --target universal2-apple-darwin
maturin build --release --target x86_64-pc-windows-msvc
twine upload target/wheels/*.whlUse GitHub Actions for the full matrix. Maturin’s docs ship a workflow template that produces wheels for all major platforms — copy that as the starting point.
Pro Tip: Always upload an sdist too: maturin sdist. If a user’s platform isn’t covered by your wheels, pip falls back to the sdist and compiles from source. Without it, niche platforms see “no matching distribution.”
Still Not Working?
A few less-obvious failures:
linker 'cc' not foundon Linux. Install build essentials:apt-get install build-essentialordnf install gcc.error: linkerlink.exenot foundon Windows. Install Visual Studio Build Tools with the “Desktop development with C++” workload. Rust on Windows uses MSVC by default.developworks butbuildproduces an empty wheel. You have apyproject.toml[project].namewith hyphens, but the Rust lib name uses underscores. Set them consistently — usually underscores in both.- Cross-compile fails with
ring/opensslsymbol errors. These C-dependent crates don’t cross-compile cleanly. Userustlsfor TLS, or build natively on each target’s runner. maturin develop --uvfails. That flag requires Maturin 1.5+ and a workinguvbinary on PATH. Update Maturin.- Different SIMD or AVX behavior between dev and prod. You built with
-C target-cpu=nativeon a Zen 4 desktop, then deployed to an older Intel server. Drop the flag for distributable wheels. UnicodeDecodeErrorreadingCargo.tomlon Windows. PowerShell saved the file as UTF-16. Re-save as UTF-8 without BOM.maturin upload403 from PyPI. Use a project-scoped API token, not your account password. Generate at pypi.org → Account settings → API tokens.
For related Python packaging and build issues, see pip could not build wheels, Python packaging not working, uv not working, and PDM not working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: PyO3 Not Working — Bound API Migration, GIL Acquisition, Error Conversion, and NumPy Interop
How to fix PyO3 errors — &PyAny vs Bound<PyAny> migration, GIL acquire/release patterns, returning Rust errors as Python exceptions, numpy ndarray zero-copy, pyclass frozen, and async tokio integration.
Fix: Pipenv Not Working — Lock File Generation, Shell Activation, and Dependency Resolution
How to fix Pipenv errors — pipenv lock takes forever, Pipfile.lock not generated, shell activation broken, no virtualenv created, dependency conflict, and migration to uv or Poetry.
Fix: PDM Not Working — Lock File Errors, PEP 582 Confusion, and Script Issues
How to fix PDM errors — pdm install fails resolving dependencies, lock file outdated warning, __pypackages__ vs venv confusion, pdm run script not found, build backend missing, and dependency groups setup.
Fix: Hatch Not Working — Environment Errors, Build Backend, and pyproject.toml Issues
How to fix Hatch errors — hatch env create fails, scripts not found, build backend hatchling missing, version not detected, plugin install errors, and publishing to PyPI.