PyO3 / pyo3

Rust bindings for the Python interpreter
https://pyo3.rs
Apache License 2.0
12.37k stars 763 forks source link

Unable to implement FromPyObject for #[pyclass] with Clone #4337

Open datapythonista opened 4 months ago

datapythonista commented 4 months ago

Bug Description

Seems intentional, but a #[pyclass] that implements Clone automatically seems to implement FromPyObject, which then can not be implemented again for my class.

I have a struct (Foo in the example) that I want to use as a #[pyfunction] argument and be able to receive many different Python types that will be converted in different ways to an instance of Foo. In my real case this is an expression in DataFusion which can be many custom classes or Python types. Things seem to work as expected, being able to implement the casting from the different types in FromPyObject. Except that my class needs to implement Clone, and then I don't seem to have a way to define how objects are converted to Foo.

Steps to Reproduce

use pyo3::prelude::*;      

#[derive(Debug, Clone)]  // Removing Clone here, everything works as expected
#[pyclass]      
struct Foo {    
    pub value: i64,      
}       

// If I remove this it compiles, but calling in Python: `>>> recive_from_int(42)`
// causes this error: `TypeError: argument 'foo': 'int' object cannot be converted to 'Foo'`
impl<'py> FromPyObject<'py> for Foo {    
    fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {    
        let value: i64 = obj.extract()?;    
        Ok(Foo { value })    
    }       
}       

#[pyfunction]      
fn receive_foo_from_int(foo: Foo) -> PyResult<i64> {    
    Ok(foo.value)                                                              
}      

#[pymodule]      
fn auto_cast(m: &Bound<'_, PyModule>) -> PyResult<()> {      
    m.add_function(wrap_pyfunction!(receive_foo_from_int, m)?)?;    
    Ok(())      
}

Backtrace

$ maturin develop
🔗 Found pyo3 bindings
🐍 Found CPython 3.12 at /home/mgarcia/.miniforge3/envs/data/bin/python
📡 Using build options features from pyproject.toml
   Compiling auto_cast v0.1.0 (/home/mgarcia/src/auto_cast)
error[E0119]: conflicting implementations of trait `pyo3::FromPyObject<'_>` for type `Foo`
 --> src/lib.rs:9:1
  |
9 | impl<'py> FromPyObject<'py> for Foo {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: conflicting implementation in crate `pyo3`:
          - impl<T> pyo3::FromPyObject<'_> for T
            where T: PyClass, T: Clone;

For more information about this error, try `rustc --explain E0119`.
error: could not compile `auto_cast` (lib) due to 1 previous error
💥 maturin failed
  Caused by: Failed to build a native library through cargo
  Caused by: Cargo build finished with "exit status: 101": `env -u CARGO PYO3_ENVIRONMENT_SIGNATURE="cpython-3.12-64bit" PYO3_PYTHON="/home/mgarcia/.miniforge3/envs/data/bin/python" PYTHON_SYS_EXECUTABLE="/home/mgarcia/.miniforge3/envs/data/bin/python" "cargo" "rustc" "--features" "pyo3/extension-module" "--message-format" "json-render-diagnostics" "--manifest-path" "/home/mgarcia/src/auto_cast/Cargo.toml" "--lib"`


### Your operating system and version

Linux 6.9.5-arch1-1

### Your Python version (`python --version`)

Python 3.12.2

### Your Rust version (`rustc --version`)

rustc 1.81.0-nightly (bcf94dec5 2024-06-23)

### Your PyO3 version

0.21.1

### How did you install python? Did you use a virtualenv?

mamba / conda-forge

### Additional Info

_No response_
marcpabst commented 2 months ago

Did you ever find a solution to this problem?

datapythonista commented 2 months ago

Did you ever find a solution to this problem?

Unfortunately I couldn't find any workaround.

marcpabst commented 2 months ago

I ended up creating an IntoX(X) wrapper and implementing FromPyObject and Into<X> for it. Seems to work well and feels quite elegant.

davidhewitt commented 2 months ago

The root cause here is we have a generic blanket impl FromPyObject for T: PyClass + Clone.

I think that blanket has caused unwelcome problems a few times now and creates subtle bugs, so I'm thinking we should consider removing. Just need to time it well around other breakages.