Search code examples
pythonrustpyo3

Regular reference vs Bound in PyO3


I'm trying to understand the way references work in PyO3 when accepting a pyclass as a function argument:

#[pyclass]
struct Number {
  #[pyo3(get, set)]
  value: i32,
}

#[pymethods]
impl Number {
    #[new]
    fn new(value: i32) -> Self {
        Number { value }
    }

    fn __repr__(&self) -> PyResult<String> {
        Ok(format!("Number({})", self.value))
    }
}

#[pyfunction]
fn increment_1(n: &mut Number>) {
    b.value += 1;
}

#[pyfunction]
fn increment_2(n: Bound<'_, Number>) {
    n.borrow_mut().value += 1;
}

#[pyfunction]
fn increment_3(n: &Bound<'_, Number>) {
    n.borrow_mut().value += 1;
}

#[pymodule]
fn mod(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_class::<Number>()?;
    m.add_function(wrap_pyfunction!(increment_1, m)?)?;
    m.add_function(wrap_pyfunction!(increment_2, m)?)?;
    m.add_function(wrap_pyfunction!(increment_3, m)?)?;
    Ok(())
}

The three version of increment_ accept a reference to Number - one as a regular reference and the other two using Bound.
They all seem to work:

from mod import Number, increment_1, increment_2, increment_3

n = Number(1)
print(n) # Number(1)

increment_1(n)
print(n) # Number(2)

increment_2(n)
print(n) # Number(3)

increment_3(n)
print(n) # Number(4)

So what's the difference between the versions? Which one is preferred?


Solution

  • Taking direct references is deprecated, due to being unsound and less performant. It is gated behind the gil-refs feature, which is supposed to be added temporarily as you upgrade your PyO3 version from an old one and then removed when you finish migration of all code to Bound.
    It is going to be removed in the next major PyO3 version.

    For more information see this github issue.

    &Bound is just a bit more efficient as it doesn't need to bump the refcount.
    Bound on the other hand is more convenient when you need an owned Bound.