// TODO https://github.com/PyO3/pyo3/issues/5487 #![allow(clippy::undocumented_unsafe_blocks)] //! PyO3's interior mutability primitive. //! //! Rust has strict aliasing rules - you can either have any number of immutable (shared) references or one mutable //! reference. Python's ownership model is the complete opposite of that - any Python object //! can be referenced any number of times, and mutation is allowed from any reference. //! //! PyO3 deals with these differences by employing the [Interior Mutability] //! pattern. This requires that PyO3 enforces the borrowing rules and it has two mechanisms for //! doing so: //! - Statically it can enforce thread-safe access with the [`Python<'py>`](crate::Python) token. //! All Rust code holding that token, or anything derived from it, can assume that they have //! safe access to the Python interpreter's state. For this reason all the native Python objects //! can be mutated through shared references. //! - However, methods and functions in Rust usually *do* need `&mut` references. While PyO3 can //! use the [`Python<'py>`](crate::Python) token to guarantee thread-safe access to them, it cannot //! statically guarantee uniqueness of `&mut` references. As such those references have to be tracked //! dynamically at runtime, using `PyCell` and the other types defined in this module. This works //! similar to std's [`RefCell`](core::cell::RefCell) type. //! //! # When *not* to use PyCell //! //! Usually you can use `&mut` references as method and function receivers and arguments, and you //! won't need to use `PyCell` directly: //! //! ```rust,no_run //! use pyo3::prelude::*; //! //! #[pyclass] //! struct Number { //! inner: u32, //! } //! //! #[pymethods] //! impl Number { //! fn increment(&mut self) { //! self.inner += 1; //! } //! } //! ``` //! //! The [`#[pymethods]`](crate::pymethods) proc macro will generate this wrapper function (and more), //! using `PyCell` under the hood: //! //! ```rust,ignore //! # use pyo3::prelude::*; //! # #[pyclass] //! # struct Number { //! # inner: u32, //! # } //! # //! # #[pymethods] //! # impl Number { //! # fn increment(&mut self) { //! # self.inner += 1; //! # } //! # } //! # //! // The function which is exported to Python looks roughly like the following //! unsafe extern "C" fn __pymethod_increment__( //! _slf: *mut pyo3::ffi::PyObject, //! _args: *mut pyo3::ffi::PyObject, //! ) -> *mut pyo3::ffi::PyObject { //! use :: pyo3 as _pyo3; //! _pyo3::impl_::trampoline::noargs(_slf, _args, |py, _slf| { //! # #[allow(deprecated)] //! let _cell = py //! .from_borrowed_ptr::<_pyo3::PyAny>(_slf) //! .cast::<_pyo3::PyCell>()?; //! let mut _ref = _cell.try_borrow_mut()?; //! let _slf: &mut Number = &mut *_ref; //! _pyo3::impl_::callback::convert(py, Number::increment(_slf)) //! }) //! } //! ``` //! //! # When to use PyCell //! ## Using pyclasses from Rust //! //! However, we *do* need `PyCell` if we want to call its methods from Rust: //! ```rust //! # use pyo3::prelude::*; //! # //! # #[pyclass] //! # struct Number { //! # inner: u32, //! # } //! # //! # #[pymethods] //! # impl Number { //! # fn increment(&mut self) { //! # self.inner += 1; //! # } //! # } //! # fn main() -> PyResult<()> { //! Python::attach(|py| { //! let n = Py::new(py, Number { inner: 0 })?; //! //! // We borrow the guard and then dereference //! // it to get a mutable reference to Number //! let mut guard: PyRefMut<'_, Number> = n.bind(py).borrow_mut(); //! let n_mutable: &mut Number = &mut *guard; //! //! n_mutable.increment(); //! //! // To avoid panics we must dispose of the //! // `PyRefMut` before borrowing again. //! drop(guard); //! //! let n_immutable: &Number = &n.bind(py).borrow(); //! assert_eq!(n_immutable.inner, 1); //! //! Ok(()) //! }) //! # } //! ``` //! ## Dealing with possibly overlapping mutable references //! //! It is also necessary to use `PyCell` if you can receive mutable arguments that may overlap. //! Suppose the following function that swaps the values of two `Number`s: //! ``` //! # use pyo3::prelude::*; //! # #[pyclass] //! # pub struct Number { //! # inner: u32, //! # } //! #[pyfunction] //! fn swap_numbers(a: &mut Number, b: &mut Number) { //! core::mem::swap(&mut a.inner, &mut b.inner); //! } //! # fn main() { //! # Python::attach(|py| { //! # let n = Py::new(py, Number{inner: 35}).unwrap(); //! # let n2 = n.clone_ref(py); //! # assert!(n.is(&n2)); //! # let fun = pyo3::wrap_pyfunction!(swap_numbers, py).unwrap(); //! # fun.call1((n, n2)).expect_err("Managed to create overlapping mutable references. Note: this is undefined behaviour."); //! # }); //! # } //! ``` //! When users pass in the same `Number` as both arguments, one of the mutable borrows will //! fail and raise a `RuntimeError`: //! ```text //! >>> a = Number() //! >>> swap_numbers(a, a) //! Traceback (most recent call last): //! File "", line 1, in //! RuntimeError: Already borrowed //! ``` //! //! It is better to write that function like this: //! ```rust,ignore //! # #![allow(deprecated)] //! # use pyo3::prelude::*; //! # #[pyclass] //! # pub struct Number { //! # inner: u32, //! # } //! #[pyfunction] //! fn swap_numbers(a: &PyCell, b: &PyCell) { //! // Check that the pointers are unequal //! if !a.is(b) { //! core::mem::swap(&mut a.borrow_mut().inner, &mut b.borrow_mut().inner); //! } else { //! // Do nothing - they are the same object, so don't need swapping. //! } //! } //! # fn main() { //! # // With duplicate numbers //! # Python::attach(|py| { //! # let n = Py::new(py, Number{inner: 35}).unwrap(); //! # let n2 = n.clone_ref(py); //! # assert!(n.is(&n2)); //! # let fun = pyo3::wrap_pyfunction!(swap_numbers, py).unwrap(); //! # fun.call1((n, n2)).unwrap(); //! # }); //! # //! # // With two different numbers //! # Python::attach(|py| { //! # let n = Py::new(py, Number{inner: 35}).unwrap(); //! # let n2 = Py::new(py, Number{inner: 42}).unwrap(); //! # assert!(!n.is(&n2)); //! # let fun = pyo3::wrap_pyfunction!(swap_numbers, py).unwrap(); //! # fun.call1((&n, &n2)).unwrap(); //! # let n: u32 = n.borrow(py).inner; //! # let n2: u32 = n2.borrow(py).inner; //! # assert_eq!(n, 42); //! # assert_eq!(n2, 35); //! # }); //! # } //! ``` //! See the [guide] for more information. //! //! [guide]: https://pyo3.rs/latest/class.html#pycell-and-interior-mutability "PyCell and interior mutability" //! [Interior Mutability]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html "RefCell and the Interior Mutability Pattern - The Rust Programming Language" use crate::conversion::IntoPyObject; use crate::exceptions::PyRuntimeError; use crate::ffi_ptr_ext::FfiPtrExt; use crate::pyclass::{boolean_struct::False, PyClass}; use crate::{ffi, Borrowed, Bound, PyErr, Python}; use core::convert::Infallible; use core::fmt; use core::mem::ManuallyDrop; use core::ops::{Deref, DerefMut}; use core::ptr::NonNull; pub(crate) mod impl_; #[cfg(feature = "experimental-inspect")] use crate::inspect::PyStaticExpr; use impl_::{PyClassBorrowChecker, PyClassObjectBaseLayout, PyClassObjectLayout}; /// A wrapper type for an immutably borrowed value from a [`Bound<'py, T>`]. /// /// See the [`Bound`] documentation for more information. /// /// # Examples /// /// You can use [`PyRef`] as an alternative to a `&self` receiver when /// - you need to access the pointer of the [`Bound`], or /// - you want to get a super class. /// ``` /// # use pyo3::prelude::*; /// #[pyclass(subclass)] /// struct Parent { /// basename: &'static str, /// } /// /// #[pyclass(extends=Parent)] /// struct Child { /// name: &'static str, /// } /// /// #[pymethods] /// impl Child { /// #[new] /// fn new() -> PyClassInitializer { /// PyClassInitializer::from(Parent { basename: "Butterfly" }) /// .add_subclass(Child { name: "Caterpillar" }) /// } /// /// fn format(slf: PyRef<'_, Self>) -> String { /// // We can get *mut ffi::PyObject from PyRef /// let refcnt = unsafe { pyo3::ffi::Py_REFCNT(slf.as_ptr()) }; /// // We can get &Self::BaseType by as_ref /// let basename = slf.as_ref().basename; /// format!("{}(base: {}, cnt: {})", slf.name, basename, refcnt) /// } /// } /// # Python::attach(|py| { /// # let sub = Py::new(py, Child::new()).unwrap(); /// # pyo3::py_run!(py, sub, "assert sub.format() == 'Caterpillar(base: Butterfly, cnt: 4)', sub.format()"); /// # }); /// ``` /// /// See the [module-level documentation](self) for more information. #[repr(transparent)] pub struct PyRef<'p, T: PyClass> { // TODO: once the GIL Ref API is removed, consider adding a lifetime parameter to `PyRef` to // store `Borrowed` here instead, avoiding reference counting overhead. inner: Bound<'p, T>, } impl<'p, T: PyClass> PyRef<'p, T> { /// Returns a `Python` token that is bound to the lifetime of the `PyRef`. pub fn py(&self) -> Python<'p> { self.inner.py() } } impl AsRef for PyRef<'_, T> where T: PyClass, U: PyClass, { fn as_ref(&self) -> &T::BaseType { self.as_super() } } impl<'py, T: PyClass> PyRef<'py, T> { /// Returns the raw FFI pointer represented by self. /// /// # Safety /// /// Callers are responsible for ensuring that the pointer does not outlive self. /// /// The reference is borrowed; callers should not decrease the reference count /// when they are finished with the pointer. #[inline] pub fn as_ptr(&self) -> *mut ffi::PyObject { self.inner.as_ptr() } /// Returns an owned raw FFI pointer represented by self. /// /// # Safety /// /// The reference is owned; when finished the caller should either transfer ownership /// of the pointer or decrease the reference count (e.g. with [`pyo3::ffi::Py_DecRef`](crate::ffi::Py_DecRef)). #[inline] pub fn into_ptr(self) -> *mut ffi::PyObject { self.inner.clone().into_ptr() } #[track_caller] pub(crate) fn borrow(obj: &Bound<'py, T>) -> Self { Self::try_borrow(obj).expect("Already mutably borrowed") } pub(crate) fn try_borrow(obj: &Bound<'py, T>) -> Result { let cell = obj.get_class_object(); cell.ensure_threadsafe(); cell.borrow_checker() .try_borrow() .map(|_| Self { inner: obj.clone() }) } } impl<'p, T> PyRef<'p, T> where T: PyClass, T::BaseType: PyClass, { /// Gets a `PyRef`. /// /// While `as_ref()` returns a reference of type `&T::BaseType`, this cannot be /// used to get the base of `T::BaseType`. /// /// But with the help of this method, you can get hold of instances of the /// super-superclass when needed. /// /// # Examples /// ``` /// # use pyo3::prelude::*; /// #[pyclass(subclass)] /// struct Base1 { /// name1: &'static str, /// } /// /// #[pyclass(extends=Base1, subclass)] /// struct Base2 { /// name2: &'static str, /// } /// /// #[pyclass(extends=Base2)] /// struct Sub { /// name3: &'static str, /// } /// /// #[pymethods] /// impl Sub { /// #[new] /// fn new() -> PyClassInitializer { /// PyClassInitializer::from(Base1 { name1: "base1" }) /// .add_subclass(Base2 { name2: "base2" }) /// .add_subclass(Self { name3: "sub" }) /// } /// fn name(slf: PyRef<'_, Self>) -> String { /// let subname = slf.name3; /// let super_ = slf.into_super(); /// format!("{} {} {}", super_.as_ref().name1, super_.name2, subname) /// } /// } /// # Python::attach(|py| { /// # let sub = Py::new(py, Sub::new()).unwrap(); /// # pyo3::py_run!(py, sub, "assert sub.name() == 'base1 base2 sub'") /// # }); /// ``` pub fn into_super(self) -> PyRef<'p, T::BaseType> { let py = self.py(); let t_not_frozen = !::VALUE; let u_frozen = <::Frozen as crate::pyclass::boolean_struct::private::Boolean>::VALUE; if t_not_frozen && u_frozen { // If `T` is mutable subclass of `U` differ, then it is possible that we need to // release the borrow count now. (e.g. `U` may have a noop borrow checker so // dropping the `PyRef` later would noop and leak the borrow we currently hold.) // // However it's nontrivial, if `U` itself has a mutable base class `V`, // then the borrow checker of both `T` and `U` is the shared borrow checker of `V`. // // But it's really hard to prove that in the type system, the soundest thing we // can do is just add a borrow to `U` now and then release the borrow of `T`. self.inner .as_super() .get_class_object() .borrow_checker() .try_borrow() .expect("this object is already borrowed"); self.inner .get_class_object() .borrow_checker() .release_borrow() }; PyRef { inner: unsafe { ManuallyDrop::new(self) .as_ptr() .assume_owned_unchecked(py) .cast_into_unchecked() }, } } /// Borrows a shared reference to `PyRef`. /// /// With the help of this method, you can access attributes and call methods /// on the superclass without consuming the `PyRef`. This method can also /// be chained to access the super-superclass (and so on). /// /// # Examples /// ``` /// # use pyo3::prelude::*; /// #[pyclass(subclass)] /// struct Base { /// base_name: &'static str, /// } /// #[pymethods] /// impl Base { /// fn base_name_len(&self) -> usize { /// self.base_name.len() /// } /// } /// /// #[pyclass(extends=Base)] /// struct Sub { /// sub_name: &'static str, /// } /// /// #[pymethods] /// impl Sub { /// #[new] /// fn new() -> PyClassInitializer { /// PyClassInitializer::from(Base { base_name: "base_name" }) /// .add_subclass(Self { sub_name: "sub_name" }) /// } /// fn sub_name_len(&self) -> usize { /// self.sub_name.len() /// } /// fn format_name_lengths(slf: PyRef<'_, Self>) -> String { /// format!("{} {}", slf.as_super().base_name_len(), slf.sub_name_len()) /// } /// } /// # Python::attach(|py| { /// # let sub = Py::new(py, Sub::new()).unwrap(); /// # pyo3::py_run!(py, sub, "assert sub.format_name_lengths() == '9 8'") /// # }); /// ``` pub fn as_super(&self) -> &PyRef<'p, T::BaseType> { let ptr = NonNull::from(&self.inner) // `Bound` has the same layout as `Bound` .cast::>() // `Bound` has the same layout as `PyRef` .cast::>(); // SAFETY: lifetimes are correctly transferred, and `PyRef` and `PyRef` have the same layout unsafe { ptr.as_ref() } } } impl Deref for PyRef<'_, T> { type Target = T; #[inline] fn deref(&self) -> &T { unsafe { &*self.inner.get_class_object().get_ptr() } } } impl Drop for PyRef<'_, T> { fn drop(&mut self) { self.inner .get_class_object() .borrow_checker() .release_borrow() } } impl<'py, T: PyClass> IntoPyObject<'py> for PyRef<'py, T> { type Target = T; type Output = Bound<'py, T>; type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self.inner.clone()) } } impl<'a, 'py, T: PyClass> IntoPyObject<'py> for &'a PyRef<'py, T> { type Target = T; type Output = Borrowed<'a, 'py, T>; type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self.inner.as_borrowed()) } } impl fmt::Debug for PyRef<'_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&**self, f) } } impl<'py, T: PyClass> TryFrom<&Bound<'py, T>> for PyRef<'py, T> { type Error = PyBorrowError; #[inline] fn try_from(value: &Bound<'py, T>) -> Result { PyRef::try_borrow(value) } } /// A wrapper type for a mutably borrowed value from a [`Bound<'py, T>`]. /// /// See the [module-level documentation](self) for more information. #[repr(transparent)] pub struct PyRefMut<'p, T: PyClass> { // TODO: once the GIL Ref API is removed, consider adding a lifetime parameter to `PyRef` to // store `Borrowed` here instead, avoiding reference counting overhead. inner: Bound<'p, T>, } impl<'p, T: PyClass> PyRefMut<'p, T> { /// Returns a `Python` token that is bound to the lifetime of the `PyRefMut`. pub fn py(&self) -> Python<'p> { self.inner.py() } } impl AsRef for PyRefMut<'_, T> where T: PyClass, T::BaseType: PyClass, { fn as_ref(&self) -> &T::BaseType { PyRefMut::downgrade(self).as_super() } } impl AsMut for PyRefMut<'_, T> where T: PyClass, T::BaseType: PyClass, { fn as_mut(&mut self) -> &mut T::BaseType { self.as_super() } } impl<'py, T: PyClass> PyRefMut<'py, T> { /// Returns the raw FFI pointer represented by self. /// /// # Safety /// /// Callers are responsible for ensuring that the pointer does not outlive self. /// /// The reference is borrowed; callers should not decrease the reference count /// when they are finished with the pointer. #[inline] pub fn as_ptr(&self) -> *mut ffi::PyObject { self.inner.as_ptr() } /// Returns an owned raw FFI pointer represented by self. /// /// # Safety /// /// The reference is owned; when finished the caller should either transfer ownership /// of the pointer or decrease the reference count (e.g. with [`pyo3::ffi::Py_DecRef`](crate::ffi::Py_DecRef)). #[inline] pub fn into_ptr(self) -> *mut ffi::PyObject { self.inner.clone().into_ptr() } #[inline] #[track_caller] pub(crate) fn borrow(obj: &Bound<'py, T>) -> Self { Self::try_borrow(obj).expect("Already borrowed") } pub(crate) fn try_borrow(obj: &Bound<'py, T>) -> Result { let cell = obj.get_class_object(); cell.ensure_threadsafe(); cell.borrow_checker() .try_borrow_mut() .map(|_| Self { inner: obj.clone() }) } pub(crate) fn downgrade(slf: &Self) -> &PyRef<'py, T> { let ptr = NonNull::from(slf).cast(); // SAFETY: `PyRefMut` and `PyRef` have the same layout unsafe { ptr.as_ref() } } } impl<'p, T> PyRefMut<'p, T> where T: PyClass, T::BaseType: PyClass, { /// Gets a `PyRef`. /// /// See [`PyRef::into_super`] for more. pub fn into_super(self) -> PyRefMut<'p, T::BaseType> { let py = self.py(); PyRefMut { inner: unsafe { ManuallyDrop::new(self) .as_ptr() .assume_owned_unchecked(py) .cast_into_unchecked() }, } } /// Borrows a mutable reference to `PyRefMut`. /// /// With the help of this method, you can mutate attributes and call mutating /// methods on the superclass without consuming the `PyRefMut`. This method /// can also be chained to access the super-superclass (and so on). /// /// See [`PyRef::as_super`] for more. pub fn as_super(&mut self) -> &mut PyRefMut<'p, T::BaseType> { let mut ptr = NonNull::from(&mut self.inner) // `Bound` has the same layout as `Bound` .cast::>() // `Bound` has the same layout as `PyRefMut`, // and the mutable borrow on `self` prevents aliasing .cast::>(); // SAFETY: lifetimes are correctly transferred, and `PyRefMut` and `PyRefMut` have the same layout unsafe { ptr.as_mut() } } } impl> Deref for PyRefMut<'_, T> { type Target = T; #[inline] fn deref(&self) -> &T { unsafe { &*self.inner.get_class_object().get_ptr() } } } impl> DerefMut for PyRefMut<'_, T> { #[inline] fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.inner.get_class_object().get_ptr() } } } impl> Drop for PyRefMut<'_, T> { fn drop(&mut self) { self.inner .get_class_object() .borrow_checker() .release_borrow_mut() } } impl<'py, T: PyClass> IntoPyObject<'py> for PyRefMut<'py, T> { type Target = T; type Output = Bound<'py, T>; type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self.inner.clone()) } } impl<'a, 'py, T: PyClass> IntoPyObject<'py> for &'a PyRefMut<'py, T> { type Target = T; type Output = Borrowed<'a, 'py, T>; type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self.inner.as_borrowed()) } } impl + fmt::Debug> fmt::Debug for PyRefMut<'_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.deref(), f) } } impl<'py, T: PyClass> TryFrom<&Bound<'py, T>> for PyRefMut<'py, T> { type Error = PyBorrowMutError; #[inline] fn try_from(value: &Bound<'py, T>) -> Result { PyRefMut::try_borrow(value) } } /// An error type returned by [`Bound::try_borrow`]. /// /// If this error is allowed to bubble up into Python code it will raise a `RuntimeError`. pub struct PyBorrowError { _private: (), } impl PyBorrowError { pub(crate) fn new() -> Self { Self { _private: () } } } impl fmt::Debug for PyBorrowError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PyBorrowError").finish() } } impl fmt::Display for PyBorrowError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt("Already mutably borrowed", f) } } impl From for PyErr { fn from(other: PyBorrowError) -> Self { PyRuntimeError::new_err(other.to_string()) } } /// An error type returned by [`Bound::try_borrow_mut`]. /// /// If this error is allowed to bubble up into Python code it will raise a `RuntimeError`. pub struct PyBorrowMutError { _private: (), } impl PyBorrowMutError { pub(crate) fn new() -> Self { Self { _private: () } } } impl fmt::Debug for PyBorrowMutError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PyBorrowMutError").finish() } } impl fmt::Display for PyBorrowMutError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt("Already borrowed", f) } } impl From for PyErr { fn from(other: PyBorrowMutError) -> Self { PyRuntimeError::new_err(other.to_string()) } } #[cfg(test)] #[cfg(feature = "macros")] mod tests { use super::*; #[crate::pyclass(skip_from_py_object)] #[pyo3(crate = "crate")] #[derive(Copy, Clone, PartialEq, Eq, Debug)] struct SomeClass(i32); #[test] fn test_as_ptr() { Python::attach(|py| { let cell = Bound::new(py, SomeClass(0)).unwrap(); let ptr = cell.as_ptr(); assert_eq!(cell.borrow().as_ptr(), ptr); assert_eq!(cell.borrow_mut().as_ptr(), ptr); }) } #[test] fn test_into_ptr() { Python::attach(|py| { let cell = Bound::new(py, SomeClass(0)).unwrap(); let ptr = cell.as_ptr(); assert_eq!(cell.borrow().into_ptr(), ptr); unsafe { ffi::Py_DECREF(ptr) }; assert_eq!(cell.borrow_mut().into_ptr(), ptr); unsafe { ffi::Py_DECREF(ptr) }; }) } #[crate::pyclass] #[pyo3(crate = "crate", subclass)] struct BaseClass { val1: usize, } #[crate::pyclass] #[pyo3(crate = "crate", extends=BaseClass, subclass)] struct SubClass { val2: usize, } #[crate::pyclass] #[pyo3(crate = "crate", extends=SubClass)] struct SubSubClass { val3: usize, } #[crate::pymethods] #[pyo3(crate = "crate")] impl SubSubClass { #[new] fn new(py: Python<'_>) -> crate::Py { let init = crate::PyClassInitializer::from(BaseClass { val1: 10 }) .add_subclass(SubClass { val2: 15 }) .add_subclass(SubSubClass { val3: 20 }); crate::Py::new(py, init).expect("allocation error") } fn get_values(self_: PyRef<'_, Self>) -> (usize, usize, usize) { let val1 = self_.as_super().as_super().val1; let val2 = self_.as_super().val2; (val1, val2, self_.val3) } fn double_values(mut self_: PyRefMut<'_, Self>) { self_.as_super().as_super().val1 *= 2; self_.as_super().val2 *= 2; self_.val3 *= 2; } } #[test] fn test_pyref_as_super() { Python::attach(|py| { let obj = SubSubClass::new(py).into_bound(py); let pyref = obj.borrow(); assert_eq!(pyref.as_super().as_super().val1, 10); assert_eq!(pyref.as_super().val2, 15); assert_eq!(pyref.as_ref().val2, 15); // `as_ref` also works assert_eq!(pyref.val3, 20); assert_eq!(SubSubClass::get_values(pyref), (10, 15, 20)); }); } #[test] fn test_pyrefmut_as_super() { Python::attach(|py| { let obj = SubSubClass::new(py).into_bound(py); assert_eq!(SubSubClass::get_values(obj.borrow()), (10, 15, 20)); { let mut pyrefmut = obj.borrow_mut(); assert_eq!(pyrefmut.as_super().as_ref().val1, 10); pyrefmut.as_super().as_super().val1 -= 5; pyrefmut.as_super().val2 -= 3; pyrefmut.as_mut().val2 -= 2; // `as_mut` also works pyrefmut.val3 -= 5; } assert_eq!(SubSubClass::get_values(obj.borrow()), (5, 10, 15)); SubSubClass::double_values(obj.borrow_mut()); assert_eq!(SubSubClass::get_values(obj.borrow()), (10, 20, 30)); }); } #[test] fn test_pyrefs_in_python() { Python::attach(|py| { let obj = SubSubClass::new(py); crate::py_run!(py, obj, "assert obj.get_values() == (10, 15, 20)"); crate::py_run!(py, obj, "assert obj.double_values() is None"); crate::py_run!(py, obj, "assert obj.get_values() == (20, 30, 40)"); }); } #[test] fn test_into_frozen_super_released_borrow() { #[crate::pyclass] #[pyo3(crate = "crate", subclass, frozen)] struct BaseClass {} #[crate::pyclass] #[pyo3(crate = "crate", extends=BaseClass, subclass)] struct SubClass {} #[crate::pymethods] #[pyo3(crate = "crate")] impl SubClass { #[new] fn new(py: Python<'_>) -> Bound<'_, SubClass> { let init = crate::PyClassInitializer::from(BaseClass {}).add_subclass(SubClass {}); Bound::new(py, init).expect("allocation error") } } Python::attach(|py| { let obj = SubClass::new(py); drop(obj.borrow().into_super()); assert!(obj.try_borrow_mut().is_ok()); }) } #[test] fn test_into_frozen_super_mutable_base_holds_borrow() { #[crate::pyclass] #[pyo3(crate = "crate", subclass)] struct BaseClass {} #[crate::pyclass] #[pyo3(crate = "crate", extends=BaseClass, subclass, frozen)] struct SubClass {} #[crate::pyclass] #[pyo3(crate = "crate", extends=SubClass, subclass)] struct SubSubClass {} #[crate::pymethods] #[pyo3(crate = "crate")] impl SubSubClass { #[new] fn new(py: Python<'_>) -> Bound<'_, SubSubClass> { let init = crate::PyClassInitializer::from(BaseClass {}) .add_subclass(SubClass {}) .add_subclass(SubSubClass {}); Bound::new(py, init).expect("allocation error") } } Python::attach(|py| { let obj = SubSubClass::new(py); let _super_borrow = obj.borrow().into_super(); // the whole object still has an immutable borrow, so we cannot // borrow any part mutably (the borrowflag is shared) assert!(obj.try_borrow_mut().is_err()); }) } }