Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
datapythonista opened this issue Jul 11, 2024 · 4 comments
Open

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

datapythonista opened this issue Jul 11, 2024 · 4 comments
Labels

Comments

@datapythonista
Copy link
Contributor

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
Copy link

Did you ever find a solution to this problem?

@datapythonista
Copy link
Contributor Author

Did you ever find a solution to this problem?

Unfortunately I couldn't find any workaround.

@marcpabst
Copy link

marcpabst commented Sep 1, 2024

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
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants