6

From Python, I want to call a Rust function that returns a Python object:

my_rust_module.my_function()  # => <object>

I am not sure how to create this function since the PYO3 guide on class instantiation describes how to instantiate such an object as a PyRef, however I can't work out how to return such a reference from a pyfunction.

This is what I am trying:

#[pyfunction]
fn my_function(py: Python) -> PyRef {
    let gil = Python::acquire_gil();
    let py = gil.python();
    PyRef::new(py, MyStruct { }).unwrap()
}

However, PyRef does not seem to be a valid return type (compiler says "wrong number of type arguments: expected 1, found 0"), and I don't know how to convert a PyRef into something that can be returned such as a PyObject.

hoefling
  • 59,418
  • 12
  • 147
  • 194
Ginty
  • 3,483
  • 20
  • 24

1 Answers1

3

Given the proper scaffolding is in place, such as pymodule and pyclass definitions, there are multiple ways to accomplish this:

#[pyfunction]
fn my_function() -> PyResult<PyObject> {
    let gil = Python::acquire_gil();
    let py = gil.python();
    let pyref = PyRef::new(py, MyStruct {})?;

    Ok(pyref.to_object(py))
}

Or:

#[pyfunction]
fn my_function() -> PyResult<Py<MyStruct>> {
    let gil = Python::acquire_gil();
    let py = gil.python();

    Py::new(py, MyStruct {})
}

I also take the liberty of wrapping returned object in PyResult in order to propagate the possible errors to python land. You can unwrap in Rust and return the naked object instead, but I'd argue this way of handling error is recommended.


Edit: the previous answer was not complete and a little bit misleading. The simplest way is actually just:

#[pyfunction]
fn my_function() -> PyResult<MyStruct> {
    Ok(MyStruct {})
}

The PYO3 pyclass guide states:

You can use pyclasses like normal rust structs.

However, if instantiated normally, you can't treat pyclasses as Python objects.

To get a Python object which includes pyclass, we have to use some special methods.

I believe this is only true if the said value doesn't cross Rust / Python boundary. If it does, pyfunction macro automatically converts the Python objects to Rust values and the Rust return value back into a Python object. The PYO3 guide could be more specific here.

Community
  • 1
  • 1
edwardw
  • 12,652
  • 3
  • 40
  • 51
  • Perfect, thank you so much! Am I right in thinking that if MyStruct does not contain any embedded references to other Python objects, then I don't need to worry about garbage collection here? i.e. the Python GC will take care of clearing this up whenever there are no more references to it on the Python side? Thanks again! – Ginty Dec 06 '19 at 09:03
  • @Ginty the answer has been updated. And yes, gc works. – edwardw Dec 11 '19 at 15:16
  • Thanks @edwardw, I see it working without the gil stuff as you describe. – Ginty Dec 11 '19 at 19:54
  • I've just noticed that there is an advantage to returning PyObject if you are implementing a function that can return different types, in that case you can cast them all to PyObject via to_object(py) – Ginty Dec 20 '19 at 22:24
  • @Ginty indeed, that is a jolly good point. Quite impress that PYO3 manages to bridge the gap of two type systems this way, one strong type and the other dynamic type. – edwardw Dec 21 '19 at 08:34