To write a python extension in rust, the easiest way is to use maturin.
It supports
- pyo3
- rust-cpython
- cffi
- uniffi bindings
- rust binaries as python packages.
To install maturin
pip install maturin
The steps to create a python extension looks like
bootstrap a maturin project (which is a rust project).
girish in ~/rust ๐ 10:16:10 โฏ mkdir projs girish in ~/rust ๐ 10:16:17 โฏ cd projs/
You can select the type of bindings you need
girish in ~/rust/projs ๐ 10:20:24 โฏ maturin new maturin_ext ? ๐คท Which kind of bindings to use? ๐ Documentation: https://maturin.rs/bindings.html โบ โฏ pyo3 rust-cpython cffi uniffi bin
then
girish in ~/rust/projs ๐ 10:16:19 โฏ maturin new maturin_ext โ ๐คท Which kind of bindings to use? ๐ Documentation: https://maturin.rs/bindings.html ยท pyo3 โจ Done! New project created maturin_ext girish in ~/rust/projs ๐ 10:16:37 โฏ ls maturin_ext/ girish in ~/rust/projs ๐ 10:16:41 โฏ tree maturin_ext/ maturin_ext/ โโโ Cargo.toml โโโ pyproject.toml โโโ src โโโ lib.rs 2 directories, 3 files girish in ~/rust/projs ๐ 10:16:44 โฏ
Now you have the boilerplate code.
The src/main.rs
looks like
use pyo3::prelude::*;
/// Formats the sum of two numbers as string.
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
/// A Python module implemented in Rust.
#[pymodule]
fn temp_(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
and the Cargo.toml
looks like
[package]
name = "maturin_ext"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "maturin_ext"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.18.0", features = ["extension-module"] }
- To build this code,
cd maturin_ext
maturin build
- Once this is done, you will get a
.whl
file undermaturin_ext/target/wheels/maturin_ext-0.1.0-cp311-cp311-manylinux_2_34_x86_64.whl
This module can be installed with pip
pip install maturin_ext/target/wheels/maturin_ext-0.1.0-cp311-cp311-manylinux_2_34_x86_64.whl
- To use this module in python
girish in ~/rust/pyenvs ๐ v3.11.2 ๐ 10:17:39 โ python Python 3.11.2 (main, Feb 8 2023, 00:00:00) [GCC 12.2.1 20221121 (Red Hat 12.2.1-4)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import maturin_ext >>> maturin_ext.sum_as_string(1, 3) '4' >>>
This way we can use maturin
to build and use the python extensions.
A few things to keep in mind. - dynamic linking:
For portability reasons, native python modules on linux must only dynamically link a set of very few libraries which are installed basically everywhere.
- source: https://github.com/PyO3/maturin#manylinux-and-auditwheel
So if your python extension depends upon some other library, you need to make sure it is included in the package (i.e. the whl
file).
Also, since python extensions make use of glibc, there could be issues running this package on alpine for python<=3.9
.
To pass the data from python to rust (and vice versa), the type conversions can be found in the user guide for PyO3
In the above example we have created a python extension in pure rust, there is no python in it. If you need some python code in the package you are shipping, the documentation can be found here