Search code examples
pythonrustpyo3maturin

Maturin project with Python bindings behind feature


I'm trying to write optional Python bindings for a Rust library, using maturin and PyO3. The default layout created by maturin is

my-project
├── Cargo.toml
├── python
│   └── my_project
│       ├── __init__.py
│       └── bar.py
├── pyproject.toml
├── README.md
└── src
    └── lib.rs

where all Rust code, including the #[pymodule] attributes go into src/lib.rs:

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 rir_generator(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    Ok(())
}

However, since I want to put all of this code behind a conditional feature, I am trying to put all of that wrapper code into src/python.rs and then import it into src/lib.rs using

#[cfg(feature = "python")]
pub mod python;

But building this fails with the warning

Warning: Couldn't find the symbol PyInit_my_project in the native library. Python will fail to import this module. If you're using pyo3, check that #[pymodule] uses my_project as module name

If I put the code back into src/lib.rs, the warning disappears.

Is there a way to put PyO3 bindings into a submodule that is then conditionally imported using features?


Solution

  • You are almost there. You need to add following section to the Cargo.toml to remove the warning.

    [features]
    default = ["python"]
    python = []
    

    Quoting from the documentation:

    Features are defined in the [features] table in Cargo.toml. Each feature specifies an array of other features or optional dependencies that it enables.

    By default, all features are disabled unless explicitly enabled. This will cause the native library to build without PyInit_<module_name> symbol, Hence the warning. This can be changed by specifying the default feature