pythonrustpyo3

How to modify Python list of custom rust objects from within Rust with PyO3?


I'm new to Rust and I have a problem. I have list in Python. It is a list of objects implemented in PyO3 Rust. I want to pass it to a function to make some changes to the elements.

This is minimal reproducible example:

use pyo3::prelude::*;

#[pyclass]
pub struct ListElement {
    pub value_sum: f32
}

#[pymethods]
impl ListElement {
    #[new]
    fn new(value_sum: f32) -> Self {
        ListElement { value_sum }
    }
    
    fn add_value(&mut self, value: f32) {
        self.value_sum += value;
    }
    
}

#[pyfunction]
fn modify_list_elements(list: Vec<&ListElement>, value: f32){
    for mut elem in list {
          elem.add_value(value);
    }
}

#[pymodule]
fn my_rust_module(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<ListElement>()?;
    m.add_function(wrap_pyfunction!(modify_list_elements, m)?)?;
    Ok(())
}

this is my Cargo.toml

[package]
name = "my_rust_module"
version = "0.1.0"
edition = "2018"

[lib]
# The name of the native library. This is the name which will be used in Python to import the
# library (i.e. `import string_sum`). If you change this, you must also change the name of the
# `#[pymodule]` in `src/lib.rs`.
name = "my_rust_module"
# "cdylib" is necessary to produce a shared library for Python to import from.
#
# Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able
# to `use string_sum;` unless the "rlib" or "lib" crate type is also included, e.g.:
# crate-type = ["cdylib", "rlib"]
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.19.0", features = ["extension-module"] }

This is error that I get when I use

maturin develop
🔗 Found pyo3 bindings
🐍 Found CPython 3.7 at C:\my_paths\python.exe
📡 Using build options features from pyproject.toml
   Compiling my_rust_module v0.1.0 (C:\my_paths\my_rust_module)
error[E0277]: the trait bound `Vec<&ListElement>: FromPyObject<'_>` is not satisfied
  --> src\lib.rs:22:31
   |
22 | fn modify_list_elements(list: Vec<&ListElement>, value: f32){
   |                               ^^^ the trait `FromPyObject<'_>` is not implemented for `Vec<&ListElement>`
   |
   = help: the trait `FromPyObject<'a>` is implemented for `Vec<T>`
   = note: required for `Vec<&ListElement>` to implement `PyFunctionArgument<'_, '_>`
note: required by a bound in `extract_argument`
  --> C:\my_paths\.cargo\registry\src\index.crates.io-6f17d22bba15001f\pyo3-0.19.0\src\impl_\extract_argument.rs:86:8
   |
86 |     T: PyFunctionArgument<'a, 'py>,
   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_argument`

For more information about this error, try `rustc --explain E0277`.

I tried to google it, understand output of rustc --explain E0277, but I still dont know how to do it. Any help would be appreciated.


Solution

  • The problem here is that Vec<T> only implements FromPyObject if T also does so, but &ListElement doesn't implement that trait.

    You can fix it in one of two ways:

    1. Derive it:
      #[pyclass]
      #[derive(FromPyObject)]
      pub struct ListElement {
          pub value_sum: f32
      }
      
    2. Take Vec<PyRefMut<ListElement>> instead for which FromPyObject is automatically implemented by the pyclass attribute:
      #[pyfunction]
      fn modify_list_elements(list: Vec<PyRefMut<ListElement>>, value: f32){
          for mut elem in list {
                elem.add_value(value);
          }
      }