2

I'm using pyo3 in Rust to create a Python module. I can create a class, and a constructor with:

    #[pyclass]
    struct MyClass {
        i: u8,
    }

    #[pymethods]
    impl MyClass {
        #[new]
        fn new() -> PyResult<Self> {
            println!("Instance Constructed!");
            Ok(MyClass { i: 0 })
        }

        // Doesn't work
        fn __del__(&mut self) {
            println!("Instance destroyed");
        }
    }

    #[pymodule]
    fn myclass(_py: Python, m: &PyModule) -> PyResult<()> {
        m.add_class::<MyClass>()?;
        Ok(())
    }

When I compile it, and use my class from Python, I can see that my instance has a __del__ method, and I can call it manually, but Python won't call it when the instance is being destroyed:

>>> from myclass import MyClass
>>> inst = MyClass()
Instance Constructed!
>>> inst.__del__
<built-in method __del__ of builtins.MyClass object at 0x766ca4c0>
>>> inst.__del__()
Instance destroyed
>>> del(inst)
>>>

Is there a special way to create a destructor in Rust using pyo3? Why would Python not call __del__? It seems to have no problems calling it for a class defined in pure Python when you del the instance.

John
  • 2,551
  • 3
  • 30
  • 55
  • 1
    Have you tried just implementing `Drop`? Also `__del__` is not really a destructor, because if the object is involved in a cycle it'll be invoked whenever python comes around to breaking the cycle. It's closer to a *finalizer*. – Masklinn Feb 27 '21 at 18:27
  • @Masklinn what is `Drop`? Some sort of Python dunder method? A `pyo3` macro, like `#[new]`? A `pyo3` trait? A rust `std` trait? Is it [this](https://doc.rust-lang.org/std/ops/trait.Drop.html)? How would one implement it? Are there any examples or documentation? – John Feb 27 '21 at 19:13
  • @Masklinn it appears that implementing `std::ops::Drop` on my class does indeed work. Is this the expected way to create a destructor when working with `pyo3`? I'm still not sure why `__del__` isn't being executed. It doesn't appear that my simple test case has any cycles that would prevent the instance from being deleted immediately upon calling `del`. If I understand you correctly, a cycle would be some sort of cyclic reference of the instance to itself, or something like that. Or does it? – John Feb 27 '21 at 19:40
  • `__del__` is a Python-level hook, pyo3 works at a lower level, that of the C API which uses the `tp_dealloc` slot. It makes a lot of sense that they'd elect to simply map `tp_dealloc` to `Drop` as that means things would usually work out of the box e.g. if you store a `Vec` in your structure, it will get properly cleaned up without having to do anything. – Masklinn Feb 27 '21 at 20:19
  • As for cycles, yes, if an object refers to itself, or an object refers to an other which refers back to it, or through more intermediates, you have a *cycle*. Because CPython uses *reference-counting*, object in a cycle will all have a reference count of 1 even if the cycle is isolated from everything else (and thus "garbage"). For that purpose Python has a limited GC process which tries to find cycles and break them (by setting one of the reference in the cycle to `None` somewhat randomly). – Masklinn Feb 27 '21 at 20:20
  • 1
    Ok, well the solution seems to work pretty good. If you want to write a short answer, I can give you credit if you'd like. Plus it will make it more obvious for others looking for an answer to the problem that there's a solution – John Feb 28 '21 at 03:53
  • Hi @John, if you don't mind sharing your implementation for `Drop`? – Jim Mar 05 '23 at 05:46

1 Answers1

0

When the Python object is released (after __del__, when the tp_dealloc() slot is called on the wrapper object) the underlying Rust struct will be dropped, so if you implement trait Drop for the struct it should be called as you expect.

(I consider this a placeholder answer, capturing info in the comments on the original question. Happy to delete this if @Masklinn would like to provide an official answer. I just didn't want others to miss this info if they happen not to read the comments.)

Peter Hansen
  • 21,046
  • 5
  • 50
  • 72