Description
One nice thing about Cython is that you can have a single .pyx
file, two lines in setup.py
and one line in requirements.txt
, and you're good. With PyO3, you need a Cargo.toml, configured a particular way (cdylib), and so it's just a bit more ceremony than Cython for simple cases.
Maturin is great for standalone libraries, but doesn't help simplify the case of adding a quick extension to an existing Python project.
To solve this, one could imagine single-file Rust extension modules, inspired by https://github.com/rust-lang/rfcs/blob/master/text/3424-cargo-script.md (and see the references to existing tools that predate the RFC, mostly for executables). You might then have:
setup.py
mypackage/
__init__.py
module.py
_extension.rs
And the setup.py
is modified to point at _extension.rs
, much like you would with Cython, rather than pointing at Cargo.toml
as it normally does:
from setuptools import setup
from setuptools_rust import Binding, RustSingleFileExtension
setup(
name="mypackage",
version="1.0",
rust_extensions=[RustSingleFileExtension("mypackage._extension", binding=Binding.PyO3)],
packages=["mypackage"],
# rust extensions are not zip safe, just like C-extensions.
zip_safe=False,
)
The _extension.rs
might look like this:
//! ```cargo
//! [dependencies]
//! pyo3 = "0.19"
//! ```
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 _extension(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
Once you start thinking about lock files this starts getting more complicated, they could be auto-generated as mypackage/_extension.rs.lock
, and you'd need some way to update them... conceivably could outsource that to Cargo eventually if the feature becomes non-experimental and they are willing to add a new use case.