2

I have a library in Rust and I want to be able to use it from Python. So I can do the bindings using PyO3.

Let's suppose that I have a following library in Rust:

pub trait Animal {
    fn make_sound();
}

pub struct Dog {}

impl Animal for Dog {
    fn make_sound() {
       println!("whoof whoof");
    }
}

pub struct Cat {}

impl Animal for Cat {
    fn make_sound() {
       println!("miaauwwww");
    }
}

pub fn make_three_sounds(&impl Animal) {
    animal.make_sound();
    animal.make_sound();
    animal.make_sound();
}

The user of that library can use the structs Dog and Cat and a function make_three_sounds. But they can also use the trait Animal to define their own animals and then pass it to the function make_three_sounds.

Now, let's say that I want this library to be used from Python. So I can use PyO3 to make the Rust -> Python bindings. However, the problem is that in Python this would be implemented differently. Instead of trait, you would have a class which you can extend. So in Python that library would look like this:

Class Animal:
    def make_sound():
        raise Error("Unimplemented abstract method")

class Dog(Animal):
    def make_sound():
        print("woaggh woafgg")

class Cat(Animal):
    def make_sound():
        print("miaaoww")

def make_three_sounds(a: Animal):
    a.make_sound()
    a.make_sound()
    a.make_sound()

In order to make a class Dog and Cat, I only need to use "#[pyclass]" and "#[pymethods]" above the Dog and Cat structs. The problem is with making an Animal class that you can extend and then pass to the function make_three_sounds. For making the Animal class, I can create a struct that will use Animal trait and then add "#[pyclass]" above that.

But the problem is: when the user of the Python library extends that class (Animal) and then pass it to make_three_sounds, then it won't work because make_three_sounds must accept a struct that implements trait Animal and that Python object doesn't implement that trait (although it's an object of a class that extends a class/struct that implements it). I can make a wrapper of make_three_sounds function that accepts PyAny object, but how will I later pass that object to the actual make_three_sounds function?

Damian
  • 178
  • 11

1 Answers1

0

One solution that I can see is as follows.

I create a function make_three_sounds(PyAny a) that will be a wrapper around the Rust make_three_sounds(&impl Animal a) function. That wrapper will accept an object of type PyAny. Later I'll do the following. I will have a special struct PyAnimal implementing trait Animal. That trait will have a property named python_object. That PyAny object from Python (that represents an object of class extending Animal) will be assigned later to that property python_object. PyAnimal will have to implement the trait methods and the implementation of this method will call that method on python_object (PyAny struct has a callmethod method which can call any method of that object). That PyAnimal object will be then passed to make_three_sounds(&impl Animal a) function. So the implementation of PyAnimal will be more or less as follows:

struct PyAnimal {
    python_object: PyAny
}

impl Animal for PyAnimal {
   fn make_sound() {
       python_object.call_method('make_sound');
   }
}
Damian
  • 178
  • 11