Properties2
TypePractice
Note createdJul 21, 2025

Even though Python is infamously slow, the ecosystem has been able to dodge that issue by using different performant languages to write the core of the packages. It is very easy to get started doing this, using maturin and following the steps presented in “Accelerate Your Python Code With Rust | the Python Exchange January 2025” by Don’t Use This Code • James Powell

Getting started

We can use the tool interface of uv, uvx, to make use of maturin. This tool will create a Rust crate with all the necessary bindings to be compatible with Python. Start with:

# Initialize the crate with pyo3 bindings
uvx maturin new -b pyo3 --src your-crate-name

Now, edit the cargo.toml to add the abi3-py38 feature, enabling compatibility from Python 3.8 onwards.

[dependencies]
# pyo3 = "0.25.0"   <- change this line for:
pyo3 = { version = "0.25.0", features = ["abi3-py38"] }

And then it’s ready to build using:

uvx maturin build
# [...]
# 📦 Built wheel for abi3 Python ≥ 3.8 to ... your-crate-name-0.1.0-cp38-abi3-macosx_11_0_arm64.whl

You can then go ahead and edit the structure to use src layout, moving all the Python code to src/ and the Rust code to rust/, which is automatically detected by maturin even though it requires some extra modifications. It boils down to marking the rust code as protected and marking the functions adequately.

Once done, you can set up your __init__.py to the recommended by maturin:

from ._your_crate_name import *
 
__doc__ = _your_crate_name.__doc__
if hasattr(_your_crate_name, "__all__"):
    __all__ = _your_crate_name.__all__

And you are ready to build and test the sample code from a Python REPL:

# $ uv run python
# Python 3.12.4 (main, Jul 25 2024, 22:11:22) [Clang 18.1.8 ] on darwin
# Type "help", "copyright", "credits" or "license" for more information.
>>> import _your_crate_name
>>> pelorus._your_crate_name(3, 4)
'7'

One last change for a quality of life improvement: make uv watch for changes in Rust files as well, in pyproject.toml:

[tool.uv]
# Rebuild the package when the Rust files change
cache-keys = [
    { file = "pyproject.toml" },
    { file = "rust/Cargo.toml" },
    { file = "**/*.rs" }
]
# Uncomment to build the Rust code in development mode
# config-settings = { build-args = "--profile=dev" }

Writing Rust code

Once the boilerplate is set, simply:

  • Add the #[pyfunction] macro before the functions that are to be exported to the Python wheel.
  • If you need to handle errors, you should return a PyResult<T>, defined as Result<T, PyError>.
  • Every time you add a function, you need to register it to a #[pymodule] with the .add_function method.