Maturin expects a particular project layout depending on the contents of the package.
For a pure Rust project, the structure is as expected and what you get from
my-rust-project/ ├── Cargo.toml ├── pyproject.toml # required for maturin configuration └── src ├── lib.rs # default for library crates └── main.rs # default for binary crates
Maturin will add a necessary
__init__.py to the package when building the
wheel. For convenience, this file includes the following:
from .my_project import * __doc__ = my_project.__doc__ if hasattr(my_project, "__all__"): __all__ = my_project.__all__
such that the module functions may be called directly with:
import my_project my_project.foo()
from my_project import my_project
Note: there is currently no way to tell maturin to include extra data (e.g.
package_datain setuptools) for a pure Rust project. Instead, consider using the layout described below for the mixed Rust/Python project.
To create a mixed Rust/Python project, add a directory with your package name
lib.name in your
Cargo.toml) to contain the Python source:
my-rust-and-python-project ├── Cargo.toml ├── my_project # <<< add this directory and put Python code in here │ ├── __init__.py │ └── bar.py ├── pyproject.toml ├── README.md └── src └── lib.rs
Note that in a mixed Rust/Python project, maturin does not modify the
__init__.py in the root package, so now to import the rust module in
Python you must use:
from my_project import my_project
You can modify
__init__.py yourself (see above) if you would like to import
Rust functions from a higher-level namespace.
You can specify a different python source directory in
pyproject.toml by setting
tool.maturin.python-source, for example
[tool.maturin] python-source = "python"
then the project structure would look like this:
my-rust-and-python-project ├── Cargo.toml ├── python │ └── my_project │ ├── __init__.py │ └── bar.py ├── pyproject.toml ├── README.md └── src └── lib.rs
This structure is recommended to avoid a common
Having a directory with
package_name in the root of the project can
occasionally cause confusion as Python allows importing local packages and
modules. A popular way to avoid this is with the
src-layout, where the Python
package is nested within a
src directory. Unfortunately this interferes with
the structure of a typical Rust project. Fortunately, Python is not particular
about the name of the parent source directory.
maturin will detect the following src layout automatically:
my-rust-and-python-project ├── src # put python code in src folder │ └── my_project │ ├── __init__.py │ └── bar.py ├── pyproject.toml ├── README.md └── rust # put rust code in rust folder |── Cargo.toml └── src └── lib.rs
If the Python module created by Rust has the same name as the Python package in a mixed Rust/Python project, IDEs might get confused.
You might also want to discourage end users from using the Rust functions directly by giving it a different name, say '_my_project'.
This can be done by adding
module-name = <package name>.<rust pymodule name> to the
[tool.maturin] in your
pyproject.toml. For example:
[tool.maturin] module-name = "my_project._my_project"
You can then import your Rust module inside your Python source as follows:
from my_project import _my_project
IDEs can then recognize the
_my_project module as separate from your main Python source module. This allows for code completion of the types inside your Rust Python module for certain IDEs.
To distribute typing information, you need to add:
- an empty marker file called
py.typedin the root of the Python package
- inline types in Python files and/or
In a pure Rust project, add type stubs in a
<module_name>.pyi file in the
project root. Maturin will automatically include this file along with the
py.typed file for you.
my-rust-project/ ├── Cargo.toml ├── my_project.pyi # <<< add type stubs for Rust functions in the my_project module here ├── pyproject.toml └── src └── lib.rs
In a mixed Rust/Python project, additional files in the Python source dir (but
.gitignore) will be automatically included in the build outputs
(source distribution and/or wheel). Type information can be therefore added to
the root Python package directory as you might do in a pure Python package.
This requires you to add the
py.typed marker file yourself.
my-project ├── Cargo.toml ├── python │ └── my_project │ ├── __init__.py │ ├── py.typed # <<< add this empty file │ ├── my_project.pyi # <<< add type stubs for Rust functions in the my_project module here │ ├── bar.pyi # <<< add type stubs for bar.py here OR type bar.py inline │ └── bar.py ├── pyproject.toml ├── README.md └── src └── lib.rs
You can add wheel data by creating a
<module_name>.data folder or setting its location as
data in pyproject.toml under
[tool.maturin] or in Cargo.toml under
The data folder may have the following subfolder:
data: The contents of this folder will simply be unpacked into the virtualenv
scripts: Treated similar to entry points, files in there are installed as standalone executable
.hC header files
purelib: This also exists, but seems to be barely used
platlib: This also exists, but seems to be barely used
If you add a symlink in the data directory, we'll include the actual file so you have more flexibility.