Appendix: PyO3 and rust-cpython

PyO3 began as fork of rust-cpython when rust-cpython wasn't maintained. Over the time pyo3 has become fundamentally different from rust-cpython.

This chapter is based on the discussion in PyO3/pyo3#55.

Macros

While rust-cpython has a macro based dsl for declaring modules and classes, PyO3 use proc macros and specialization. PyO3 also doesn't change your struct and functions so you can still use them as normal rust functions. The disadvantage is that proc macros and specialization currently only work on nightly.

rust-cpython

py_class!(class MyClass |py| {
    data number: i32;
    def __new__(_cls, arg: i32) -> PyResult<MyClass> {
        MyClass::create_instance(py, arg)
    }
    def half(&self) -> PyResult<i32> {
        Ok(self.number(py) / 2)
    }
});

pyo3


# #![allow(unused_variables)]
#fn main() {
# extern crate pyo3;
use pyo3::prelude::*;
use pyo3::PyRawObject;

#[pyclass]
struct MyClass {
   num: u32,
}

#[pymethods]
impl MyClass {
    #[new]
    fn new(obj: &PyRawObject, num: u32) {
        obj.init({
            MyClass {
                num,
            }
        });
    }

    fn half(&self) -> PyResult<u32> {
        Ok(self.num / 2)
    }
}
#}

Ownership and lifetimes

All objects are owned by PyO3 library and all apis available with references, while in rust-cpython, you own python objects.

Here is example of PyList api:

rust-cpython

impl PyList {

   fn new(py: Python) -> PyList {...}

   fn get_item(&self, py: Python, index: isize) -> PyObject {...}
}

pyo3

impl PyList {

   fn new(py: Python) -> &PyList {...}

   fn get_item(&self, index: isize) -> &PyObject {...}
}

Because PyO3 allows only references to python object, all reference have the Gil lifetime. So the python object is not required, and it is safe to have functions like fn py<'p>(&'p self) -> Python<'p> {}.

Error handling

rust-cpython requires a Python parameter for PyErr, so error handling ergonomics is pretty bad. It is not possible to use ? with rust errors.

PyO3 on other hand does not require Python for PyErr, it is only required if you want to raise an exception in python with the PyErr::restore() method. Due to the std::convert::From<Err> for PyErr trait ? is supported automatically.