Skip to main content

pyo3/pyclass/
guard.rs

1use crate::impl_::pycell::PyClassObjectBaseLayout as _;
2use crate::impl_::pyclass::PyClassImpl;
3#[cfg(feature = "experimental-inspect")]
4use crate::inspect::PyStaticExpr;
5use crate::pycell::impl_::PyClassObjectLayout as _;
6use crate::pycell::PyBorrowMutError;
7use crate::pycell::{impl_::PyClassBorrowChecker, PyBorrowError};
8use crate::pyclass::boolean_struct::False;
9use crate::{ffi, Borrowed, Bound, CastError, FromPyObject, IntoPyObject, Py, PyClass, PyErr};
10use std::convert::Infallible;
11use std::fmt;
12use std::marker::PhantomData;
13use std::ops::{Deref, DerefMut};
14use std::ptr::NonNull;
15
16/// A wrapper type for an immutably borrowed value from a `PyClass`.
17///
18/// Rust has strict aliasing rules - you can either have any number of immutable
19/// (shared) references or one mutable reference. Python's ownership model is
20/// the complete opposite of that - any Python object can be referenced any
21/// number of times, and mutation is allowed from any reference.
22///
23/// PyO3 deals with these differences by employing the [Interior Mutability]
24/// pattern. This requires that PyO3 enforces the borrowing rules and it has two
25/// mechanisms for doing so:
26/// - Statically it can enforce thread-safe access with the
27///   [`Python<'py>`](crate::Python) token. All Rust code holding that token, or
28///   anything derived from it, can assume that they have safe access to the
29///   Python interpreter's state. For this reason all the native Python objects
30///   can be mutated through shared references.
31/// - However, methods and functions in Rust usually *do* need `&mut`
32///   references. While PyO3 can use the [`Python<'py>`](crate::Python) token to
33///   guarantee thread-safe access to them, it cannot statically guarantee
34///   uniqueness of `&mut` references. As such those references have to be
35///   tracked dynamically at runtime, using [`PyClassGuard`] and
36///   [`PyClassGuardMut`] defined in this module. This works similar to std's
37///   [`RefCell`](std::cell::RefCell) type. Especially when building for
38///   free-threaded Python it gets harder to track which thread borrows which
39///   object at any time. This can lead to method calls failing with
40///   [`PyBorrowError`]. In these cases consider using `frozen` classes together
41///   with Rust interior mutability primitives like [`Mutex`](std::sync::Mutex)
42///   instead of using [`PyClassGuardMut`] to get mutable access.
43///
44/// # Examples
45///
46/// You can use [`PyClassGuard`] as an alternative to a `&self` receiver when
47/// - you need to access the pointer of the `PyClass`, or
48/// - you want to get a super class.
49/// ```
50/// # use pyo3::prelude::*;
51/// # use pyo3::PyClassGuard;
52/// #[pyclass(subclass)]
53/// struct Parent {
54///     basename: &'static str,
55/// }
56///
57/// #[pyclass(extends=Parent)]
58/// struct Child {
59///     name: &'static str,
60///  }
61///
62/// #[pymethods]
63/// impl Child {
64///     #[new]
65///     fn new() -> (Self, Parent) {
66///         (Child { name: "Caterpillar" }, Parent { basename: "Butterfly" })
67///     }
68///
69///     fn format(slf: PyClassGuard<'_, Self>) -> String {
70///         // We can get &Self::BaseType by as_super
71///         let basename = slf.as_super().basename;
72///         format!("{}(base: {})", slf.name, basename)
73///     }
74/// }
75/// # Python::attach(|py| {
76/// #     let sub = Py::new(py, Child::new()).unwrap();
77/// #     pyo3::py_run!(py, sub, "assert sub.format() == 'Caterpillar(base: Butterfly)', sub.format()");
78/// # });
79/// ```
80///
81/// See also [`PyClassGuardMut`] and the [guide] for more information.
82///
83/// [Interior Mutability]:
84///     https://doc.rust-lang.org/book/ch15-05-interior-mutability.html
85///     "RefCell<T> and the Interior Mutability Pattern - The Rust Programming
86///     Language"
87/// [guide]: https://pyo3.rs/latest/class.html#bound-and-interior-mutability
88///     "Bound and interior mutability"
89#[repr(transparent)]
90pub struct PyClassGuard<'a, T: PyClass> {
91    ptr: NonNull<ffi::PyObject>,
92    marker: PhantomData<&'a Py<T>>,
93}
94
95impl<'a, T: PyClass> PyClassGuard<'a, T> {
96    pub(crate) fn try_borrow(obj: &'a Py<T>) -> Result<Self, PyBorrowError> {
97        Self::try_from_class_object(obj.get_class_object())
98    }
99
100    pub(crate) fn try_borrow_from_borrowed(
101        obj: Borrowed<'a, '_, T>,
102    ) -> Result<Self, PyBorrowError> {
103        Self::try_from_class_object(obj.get_class_object())
104    }
105
106    fn try_from_class_object(obj: &'a <T as PyClassImpl>::Layout) -> Result<Self, PyBorrowError> {
107        obj.ensure_threadsafe();
108        obj.borrow_checker().try_borrow().map(|_| Self {
109            ptr: NonNull::from(obj).cast(),
110            marker: PhantomData,
111        })
112    }
113
114    pub(crate) fn as_class_object(&self) -> &'a <T as PyClassImpl>::Layout {
115        // SAFETY: `ptr` by construction points to a `PyClassObject<T>` and is
116        // valid for at least 'a
117        unsafe { self.ptr.cast().as_ref() }
118    }
119
120    /// Consumes the [`PyClassGuard`] and returns a [`PyClassGuardMap`] for a component of the
121    /// borrowed data
122    ///
123    /// # Examples
124    ///
125    /// ```
126    /// # use pyo3::prelude::*;
127    /// # use pyo3::PyClassGuard;
128    ///
129    /// #[pyclass]
130    /// pub struct MyClass {
131    ///     msg: String,
132    /// }
133    ///
134    /// # Python::attach(|py| {
135    /// let obj = Bound::new(py, MyClass { msg: String::from("hello") })?;
136    /// let msg = obj.extract::<PyClassGuard<'_, MyClass>>()?.map(|c| &c.msg);
137    /// assert_eq!(&*msg, "hello");
138    /// # Ok::<_, PyErr>(())
139    /// # }).unwrap();
140    /// ```
141    pub fn map<F, U: ?Sized>(self, f: F) -> PyClassGuardMap<'a, U, false>
142    where
143        F: FnOnce(&T) -> &U,
144    {
145        let slf = std::mem::ManuallyDrop::new(self); // the borrow is released when dropping the `PyClassGuardMap`
146        PyClassGuardMap {
147            ptr: NonNull::from(f(&slf)),
148            checker: slf.as_class_object().borrow_checker(),
149        }
150    }
151}
152
153impl<'a, T> PyClassGuard<'a, T>
154where
155    T: PyClass,
156    T::BaseType: PyClass,
157{
158    /// Borrows a shared reference to `PyClassGuard<T::BaseType>`.
159    ///
160    /// With the help of this method, you can access attributes and call methods
161    /// on the superclass without consuming the `PyClassGuard<T>`. This method
162    /// can also be chained to access the super-superclass (and so on).
163    ///
164    /// # Examples
165    /// ```
166    /// # use pyo3::prelude::*;
167    /// # use pyo3::PyClassGuard;
168    /// #[pyclass(subclass)]
169    /// struct Base {
170    ///     base_name: &'static str,
171    /// }
172    /// #[pymethods]
173    /// impl Base {
174    ///     fn base_name_len(&self) -> usize {
175    ///         self.base_name.len()
176    ///     }
177    /// }
178    ///
179    /// #[pyclass(extends=Base)]
180    /// struct Sub {
181    ///     sub_name: &'static str,
182    /// }
183    ///
184    /// #[pymethods]
185    /// impl Sub {
186    ///     #[new]
187    ///     fn new() -> (Self, Base) {
188    ///         (Self { sub_name: "sub_name" }, Base { base_name: "base_name" })
189    ///     }
190    ///     fn sub_name_len(&self) -> usize {
191    ///         self.sub_name.len()
192    ///     }
193    ///     fn format_name_lengths(slf: PyClassGuard<'_, Self>) -> String {
194    ///         format!("{} {}", slf.as_super().base_name_len(), slf.sub_name_len())
195    ///     }
196    /// }
197    /// # Python::attach(|py| {
198    /// #     let sub = Py::new(py, Sub::new()).unwrap();
199    /// #     pyo3::py_run!(py, sub, "assert sub.format_name_lengths() == '9 8'")
200    /// # });
201    /// ```
202    pub fn as_super(&self) -> &PyClassGuard<'a, T::BaseType> {
203        // SAFETY: `PyClassGuard<T>` and `PyClassGuard<U>` have the same layout
204        unsafe { NonNull::from(self).cast().as_ref() }
205    }
206
207    /// Gets a `PyClassGuard<T::BaseType>`.
208    ///
209    /// With the help of this method, you can get hold of instances of the
210    /// super-superclass when needed.
211    ///
212    /// # Examples
213    /// ```
214    /// # use pyo3::prelude::*;
215    /// # use pyo3::PyClassGuard;
216    /// #[pyclass(subclass)]
217    /// struct Base1 {
218    ///     name1: &'static str,
219    /// }
220    ///
221    /// #[pyclass(extends=Base1, subclass)]
222    /// struct Base2 {
223    ///     name2: &'static str,
224    /// }
225    ///
226    /// #[pyclass(extends=Base2)]
227    /// struct Sub {
228    ///     name3: &'static str,
229    /// }
230    ///
231    /// #[pymethods]
232    /// impl Sub {
233    ///     #[new]
234    ///     fn new() -> PyClassInitializer<Self> {
235    ///         PyClassInitializer::from(Base1 { name1: "base1" })
236    ///             .add_subclass(Base2 { name2: "base2" })
237    ///             .add_subclass(Self { name3: "sub" })
238    ///     }
239    ///     fn name(slf: PyClassGuard<'_, Self>) -> String {
240    ///         let subname = slf.name3;
241    ///         let super_ = slf.into_super();
242    ///         format!("{} {} {}", super_.as_super().name1, super_.name2, subname)
243    ///     }
244    /// }
245    /// # Python::attach(|py| {
246    /// #     let sub = Py::new(py, Sub::new()).unwrap();
247    /// #     pyo3::py_run!(py, sub, "assert sub.name() == 'base1 base2 sub'")
248    /// # });
249    /// ```
250    pub fn into_super(self) -> PyClassGuard<'a, T::BaseType> {
251        let t_not_frozen = !<T::Frozen as crate::pyclass::boolean_struct::private::Boolean>::VALUE;
252        let u_frozen =
253            <<T::BaseType as PyClass>::Frozen as crate::pyclass::boolean_struct::private::Boolean>::VALUE;
254        if t_not_frozen && u_frozen {
255            // If `T` is a mutable subclass of a frozen `U` base, then it is possible that we need
256            // to release the borrow count now. (e.g. `U` may have a noop borrow checker so dropping
257            // the `PyRef<U>` later would noop and leak the borrow we currently hold.)
258            //
259            // However it's nontrivial, if `U` is frozen but itself has a mutable base class `V`,
260            // then the borrow checker of both `T` and `U` is the shared borrow checker of `V`.
261            //
262            // But it's really hard to prove that in the type system, the soundest thing we can do
263            // is just add a borrow to `U` now and then release the borrow of `T`.
264
265            self.as_super()
266                .as_class_object()
267                .borrow_checker()
268                .try_borrow()
269                .expect("this object is already borrowed");
270
271            self.as_class_object().borrow_checker().release_borrow()
272        };
273        PyClassGuard {
274            ptr: std::mem::ManuallyDrop::new(self).ptr,
275            marker: PhantomData,
276        }
277    }
278}
279
280impl<T: PyClass> Deref for PyClassGuard<'_, T> {
281    type Target = T;
282
283    #[inline]
284    fn deref(&self) -> &T {
285        // SAFETY: `PyClassObject<T>` contains a valid `T`, by construction no
286        // mutable alias is enforced
287        unsafe { &*self.as_class_object().get_ptr().cast_const() }
288    }
289}
290
291impl<'a, 'py, T: PyClass> FromPyObject<'a, 'py> for PyClassGuard<'a, T> {
292    type Error = PyClassGuardError<'a, 'py>;
293
294    #[cfg(feature = "experimental-inspect")]
295    const INPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
296
297    fn extract(obj: Borrowed<'a, 'py, crate::PyAny>) -> Result<Self, Self::Error> {
298        Self::try_from_class_object(
299            obj.cast::<T>()
300                .map_err(|e| PyClassGuardError(Some(e)))?
301                .get_class_object(),
302        )
303        .map_err(|_| PyClassGuardError(None))
304    }
305}
306
307impl<'a, 'py, T: PyClass> IntoPyObject<'py> for PyClassGuard<'a, T> {
308    type Target = T;
309    type Output = Borrowed<'a, 'py, T>;
310    type Error = Infallible;
311
312    #[cfg(feature = "experimental-inspect")]
313    const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
314
315    #[inline]
316    fn into_pyobject(self, py: crate::Python<'py>) -> Result<Self::Output, Self::Error> {
317        (&self).into_pyobject(py)
318    }
319}
320
321impl<'a, 'py, T: PyClass> IntoPyObject<'py> for &PyClassGuard<'a, T> {
322    type Target = T;
323    type Output = Borrowed<'a, 'py, T>;
324    type Error = Infallible;
325
326    #[cfg(feature = "experimental-inspect")]
327    const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
328
329    #[inline]
330    fn into_pyobject(self, py: crate::Python<'py>) -> Result<Self::Output, Self::Error> {
331        // SAFETY: `ptr` is guaranteed to be valid for 'a and points to an
332        // object of type T
333        unsafe { Ok(Borrowed::from_non_null(py, self.ptr).cast_unchecked()) }
334    }
335}
336
337impl<T: PyClass> Drop for PyClassGuard<'_, T> {
338    /// Releases the shared borrow
339    fn drop(&mut self) {
340        self.as_class_object().borrow_checker().release_borrow()
341    }
342}
343
344impl<'a, 'py, T: PyClass> TryFrom<&'a Bound<'py, T>> for PyClassGuard<'a, T> {
345    type Error = PyBorrowError;
346    #[inline]
347    fn try_from(value: &'a Bound<'py, T>) -> Result<Self, Self::Error> {
348        PyClassGuard::try_borrow(value.as_unbound())
349    }
350}
351
352// SAFETY: `PyClassGuard` only provides access to the inner `T` (and no other
353// Python APIs) which does not require a Python thread state
354#[cfg(feature = "nightly")]
355unsafe impl<T: PyClass> crate::marker::Ungil for PyClassGuard<'_, T> {}
356// SAFETY: we provide access to
357// - `&T`, which requires `T: Sync` to be Send and `T: Sync` to be Sync
358unsafe impl<T: PyClass + Sync> Send for PyClassGuard<'_, T> {}
359unsafe impl<T: PyClass + Sync> Sync for PyClassGuard<'_, T> {}
360
361/// Custom error type for extracting a [PyClassGuard]
362pub struct PyClassGuardError<'a, 'py>(pub(crate) Option<CastError<'a, 'py>>);
363
364impl fmt::Debug for PyClassGuardError<'_, '_> {
365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366        if let Some(e) = &self.0 {
367            write!(f, "{e:?}")
368        } else {
369            write!(f, "{:?}", PyBorrowError::new())
370        }
371    }
372}
373
374impl fmt::Display for PyClassGuardError<'_, '_> {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        if let Some(e) = &self.0 {
377            write!(f, "{e}")
378        } else {
379            write!(f, "{}", PyBorrowError::new())
380        }
381    }
382}
383
384impl From<PyClassGuardError<'_, '_>> for PyErr {
385    fn from(value: PyClassGuardError<'_, '_>) -> Self {
386        if let Some(e) = value.0 {
387            e.into()
388        } else {
389            PyBorrowError::new().into()
390        }
391    }
392}
393
394/// A wrapper type for a mutably borrowed value from a `PyClass`
395///
396/// # When *not* to use [`PyClassGuardMut`]
397///
398/// Usually you can use `&mut` references as method and function receivers and
399/// arguments, and you won't need to use [`PyClassGuardMut`] directly:
400///
401/// ```rust,no_run
402/// use pyo3::prelude::*;
403///
404/// #[pyclass]
405/// struct Number {
406///     inner: u32,
407/// }
408///
409/// #[pymethods]
410/// impl Number {
411///     fn increment(&mut self) {
412///         self.inner += 1;
413///     }
414/// }
415/// ```
416///
417/// The [`#[pymethods]`](crate::pymethods) proc macro will generate this wrapper
418/// function (and more), using [`PyClassGuardMut`] under the hood:
419///
420/// ```rust,no_run
421/// # use pyo3::prelude::*;
422/// # #[pyclass]
423/// # struct Number {
424/// #    inner: u32,
425/// # }
426/// #
427/// # #[pymethods]
428/// # impl Number {
429/// #    fn increment(&mut self) {
430/// #        self.inner += 1;
431/// #    }
432/// # }
433/// #
434/// // The function which is exported to Python looks roughly like the following
435/// unsafe extern "C" fn __pymethod_increment__(
436///     _slf: *mut ::pyo3::ffi::PyObject,
437///     _args: *mut ::pyo3::ffi::PyObject,
438/// ) -> *mut ::pyo3::ffi::PyObject {
439///     unsafe fn inner<'py>(
440///         py: ::pyo3::Python<'py>,
441///         _slf: *mut ::pyo3::ffi::PyObject,
442///     ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> {
443///         let function = Number::increment;
444/// #       #[allow(clippy::let_unit_value)]
445///         let mut holder_0 = ::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT;
446///         let result = {
447///             let ret = function(::pyo3::impl_::extract_argument::extract_pyclass_ref_mut::<Number>(
448///                 unsafe { ::pyo3::impl_::extract_argument::cast_function_argument(py, _slf) },
449///                 &mut holder_0,
450///             )?);
451///             {
452///                 let result = {
453///                     let obj = ret;
454/// #                   #[allow(clippy::useless_conversion)]
455///                     ::pyo3::impl_::wrap::converter(&obj)
456///                         .wrap(obj)
457///                         .map_err(::core::convert::Into::<::pyo3::PyErr>::into)
458///                 };
459///                 ::pyo3::impl_::wrap::converter(&result).map_into_ptr(py, result)
460///             }
461///         };
462///         result
463///     }
464///
465///     unsafe {
466///         ::pyo3::impl_::trampoline::get_trampoline_function!(noargs, inner)(
467///             _slf,
468///             _args,
469///         )
470///     }
471/// }
472/// ```
473///
474/// # When to use [`PyClassGuardMut`]
475/// ## Using PyClasses from Rust
476///
477/// However, we *do* need [`PyClassGuardMut`] if we want to call its methods
478/// from Rust:
479/// ```rust
480/// # use pyo3::prelude::*;
481/// # use pyo3::{PyClassGuard, PyClassGuardMut};
482/// #
483/// # #[pyclass]
484/// # struct Number {
485/// #     inner: u32,
486/// # }
487/// #
488/// # #[pymethods]
489/// # impl Number {
490/// #     fn increment(&mut self) {
491/// #         self.inner += 1;
492/// #     }
493/// # }
494/// # fn main() -> PyResult<()> {
495/// Python::attach(|py| {
496///     let n = Py::new(py, Number { inner: 0 })?;
497///
498///     // We borrow the guard and then dereference
499///     // it to get a mutable reference to Number
500///     let mut guard: PyClassGuardMut<'_, Number> = n.extract(py)?;
501///     let n_mutable: &mut Number = &mut *guard;
502///
503///     n_mutable.increment();
504///
505///     // To avoid panics we must dispose of the
506///     // `PyClassGuardMut` before borrowing again.
507///     drop(guard);
508///
509///     let n_immutable: &Number = &*n.extract::<PyClassGuard<'_, Number>>(py)?;
510///     assert_eq!(n_immutable.inner, 1);
511///
512///     Ok(())
513/// })
514/// # }
515/// ```
516/// ## Dealing with possibly overlapping mutable references
517///
518/// It is also necessary to use [`PyClassGuardMut`] if you can receive mutable
519/// arguments that may overlap. Suppose the following function that swaps the
520/// values of two `Number`s:
521/// ```
522/// # use pyo3::prelude::*;
523/// # #[pyclass]
524/// # pub struct Number {
525/// #     inner: u32,
526/// # }
527/// #[pyfunction]
528/// fn swap_numbers(a: &mut Number, b: &mut Number) {
529///     std::mem::swap(&mut a.inner, &mut b.inner);
530/// }
531/// # fn main() {
532/// #     Python::attach(|py| {
533/// #         let n = Py::new(py, Number{inner: 35}).unwrap();
534/// #         let n2 = n.clone_ref(py);
535/// #         assert!(n.is(&n2));
536/// #         let fun = pyo3::wrap_pyfunction!(swap_numbers, py).unwrap();
537/// #         fun.call1((n, n2)).expect_err("Managed to create overlapping mutable references. Note: this is undefined behaviour.");
538/// #     });
539/// # }
540/// ```
541/// When users pass in the same `Number` as both arguments, one of the mutable
542/// borrows will fail and raise a `RuntimeError`:
543/// ```text
544/// >>> a = Number()
545/// >>> swap_numbers(a, a)
546/// Traceback (most recent call last):
547///   File "<stdin>", line 1, in <module>
548///   RuntimeError: Already borrowed
549/// ```
550///
551/// It is better to write that function like this:
552/// ```rust
553/// # use pyo3::prelude::*;
554/// # use pyo3::{PyClassGuard, PyClassGuardMut};
555/// # #[pyclass]
556/// # pub struct Number {
557/// #     inner: u32,
558/// # }
559/// #[pyfunction]
560/// fn swap_numbers(a: &Bound<'_, Number>, b: &Bound<'_, Number>) -> PyResult<()> {
561///     // Check that the pointers are unequal
562///     if !a.is(b) {
563///         let mut a: PyClassGuardMut<'_, Number> = a.extract()?;
564///         let mut b: PyClassGuardMut<'_, Number> = b.extract()?;
565///         std::mem::swap(&mut a.inner, &mut b.inner);
566///     } else {
567///         // Do nothing - they are the same object, so don't need swapping.
568///     }
569///     Ok(())
570/// }
571/// # fn main() {
572/// #     // With duplicate numbers
573/// #     Python::attach(|py| {
574/// #         let n = Py::new(py, Number{inner: 35}).unwrap();
575/// #         let n2 = n.clone_ref(py);
576/// #         assert!(n.is(&n2));
577/// #         let fun = pyo3::wrap_pyfunction!(swap_numbers, py).unwrap();
578/// #         fun.call1((n, n2)).unwrap();
579/// #     });
580/// #
581/// #     // With two different numbers
582/// #     Python::attach(|py| {
583/// #         let n = Py::new(py, Number{inner: 35}).unwrap();
584/// #         let n2 = Py::new(py, Number{inner: 42}).unwrap();
585/// #         assert!(!n.is(&n2));
586/// #         let fun = pyo3::wrap_pyfunction!(swap_numbers, py).unwrap();
587/// #         fun.call1((&n, &n2)).unwrap();
588/// #         let n: u32 = n.extract::<PyClassGuard<'_, Number>>(py).unwrap().inner;
589/// #         let n2: u32 = n2.extract::<PyClassGuard<'_, Number>>(py).unwrap().inner;
590/// #         assert_eq!(n, 42);
591/// #         assert_eq!(n2, 35);
592/// #     });
593/// # }
594/// ```
595/// See [`PyClassGuard`] and the [guide] for more information.
596///
597/// [guide]: https://pyo3.rs/latest/class.html#bound-and-interior-mutability
598///     "Bound and interior mutability"
599#[repr(transparent)]
600pub struct PyClassGuardMut<'a, T: PyClass<Frozen = False>> {
601    ptr: NonNull<ffi::PyObject>,
602    marker: PhantomData<&'a Py<T>>,
603}
604
605impl<'a, T: PyClass<Frozen = False>> PyClassGuardMut<'a, T> {
606    pub(crate) fn try_borrow_mut(obj: &'a Py<T>) -> Result<Self, PyBorrowMutError> {
607        Self::try_from_class_object(obj.get_class_object())
608    }
609
610    pub(crate) fn try_borrow_mut_from_borrowed(
611        obj: Borrowed<'a, '_, T>,
612    ) -> Result<Self, PyBorrowMutError> {
613        Self::try_from_class_object(obj.get_class_object())
614    }
615
616    fn try_from_class_object(
617        obj: &'a <T as PyClassImpl>::Layout,
618    ) -> Result<Self, PyBorrowMutError> {
619        obj.ensure_threadsafe();
620        obj.borrow_checker().try_borrow_mut().map(|_| Self {
621            ptr: NonNull::from(obj).cast(),
622            marker: PhantomData,
623        })
624    }
625
626    pub(crate) fn as_class_object(&self) -> &'a <T as PyClassImpl>::Layout {
627        // SAFETY: `ptr` by construction points to a `PyClassObject<T>` and is
628        // valid for at least 'a
629        unsafe { self.ptr.cast().as_ref() }
630    }
631
632    /// Consumes the [`PyClassGuardMut`] and returns a [`PyClassGuardMap`] for a component of the
633    /// borrowed data
634    ///
635    /// # Examples
636    ///
637    /// ```
638    /// # use pyo3::prelude::*;
639    /// # use pyo3::PyClassGuardMut;
640    ///
641    /// #[pyclass]
642    /// pub struct MyClass {
643    ///     data: [i32; 100],
644    /// }
645    ///
646    /// # Python::attach(|py| {
647    /// let obj = Bound::new(py, MyClass { data: [0; 100] })?;
648    /// let mut data = obj.extract::<PyClassGuardMut<'_, MyClass>>()?.map(|c| c.data.as_mut_slice());
649    /// data[0] = 42;
650    /// # Ok::<_, PyErr>(())
651    /// # }).unwrap();
652    /// ```
653    pub fn map<F, U: ?Sized>(self, f: F) -> PyClassGuardMap<'a, U, true>
654    where
655        F: FnOnce(&mut T) -> &mut U,
656    {
657        let mut slf = std::mem::ManuallyDrop::new(self); // the borrow is released when dropping the `PyClassGuardMap`
658        PyClassGuardMap {
659            ptr: NonNull::from(f(&mut slf)),
660            checker: slf.as_class_object().borrow_checker(),
661        }
662    }
663}
664
665impl<'a, T> PyClassGuardMut<'a, T>
666where
667    T: PyClass<Frozen = False>,
668    T::BaseType: PyClass<Frozen = False>,
669{
670    /// Borrows a mutable reference to `PyClassGuardMut<T::BaseType>`.
671    ///
672    /// With the help of this method, you can mutate attributes and call
673    /// mutating methods on the superclass without consuming the
674    /// `PyClassGuardMut<T>`. This method can also be chained to access the
675    /// super-superclass (and so on).
676    ///
677    /// See [`PyClassGuard::as_super`] for more.
678    pub fn as_super(&mut self) -> &mut PyClassGuardMut<'a, T::BaseType> {
679        // SAFETY: `PyClassGuardMut<T>` and `PyClassGuardMut<U>` have the same layout
680        unsafe { NonNull::from(self).cast().as_mut() }
681    }
682
683    /// Gets a `PyClassGuardMut<T::BaseType>`.
684    ///
685    /// See [`PyClassGuard::into_super`] for more.
686    pub fn into_super(self) -> PyClassGuardMut<'a, T::BaseType> {
687        // `PyClassGuardMut` is only available for non-frozen classes, so there
688        // is no possibility of leaking borrows like `PyClassGuard`
689        PyClassGuardMut {
690            ptr: std::mem::ManuallyDrop::new(self).ptr,
691            marker: PhantomData,
692        }
693    }
694}
695
696impl<T: PyClass<Frozen = False>> Deref for PyClassGuardMut<'_, T> {
697    type Target = T;
698
699    #[inline]
700    fn deref(&self) -> &T {
701        // SAFETY: `PyClassObject<T>` contains a valid `T`, by construction no
702        // alias is enforced
703        unsafe { &*self.as_class_object().get_ptr().cast_const() }
704    }
705}
706impl<T: PyClass<Frozen = False>> DerefMut for PyClassGuardMut<'_, T> {
707    #[inline]
708    fn deref_mut(&mut self) -> &mut T {
709        // SAFETY: `PyClassObject<T>` contains a valid `T`, by construction no
710        // alias is enforced
711        unsafe { &mut *self.as_class_object().get_ptr() }
712    }
713}
714
715impl<'a, 'py, T: PyClass<Frozen = False>> FromPyObject<'a, 'py> for PyClassGuardMut<'a, T> {
716    type Error = PyClassGuardMutError<'a, 'py>;
717
718    #[cfg(feature = "experimental-inspect")]
719    const INPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
720
721    fn extract(obj: Borrowed<'a, 'py, crate::PyAny>) -> Result<Self, Self::Error> {
722        Self::try_from_class_object(
723            obj.cast::<T>()
724                .map_err(|e| PyClassGuardMutError(Some(e)))?
725                .get_class_object(),
726        )
727        .map_err(|_| PyClassGuardMutError(None))
728    }
729}
730
731impl<'a, 'py, T: PyClass<Frozen = False>> IntoPyObject<'py> for PyClassGuardMut<'a, T> {
732    type Target = T;
733    type Output = Borrowed<'a, 'py, T>;
734    type Error = Infallible;
735
736    #[cfg(feature = "experimental-inspect")]
737    const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
738
739    #[inline]
740    fn into_pyobject(self, py: crate::Python<'py>) -> Result<Self::Output, Self::Error> {
741        (&self).into_pyobject(py)
742    }
743}
744
745impl<'a, 'py, T: PyClass<Frozen = False>> IntoPyObject<'py> for &PyClassGuardMut<'a, T> {
746    type Target = T;
747    type Output = Borrowed<'a, 'py, T>;
748    type Error = Infallible;
749
750    #[cfg(feature = "experimental-inspect")]
751    const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
752
753    #[inline]
754    fn into_pyobject(self, py: crate::Python<'py>) -> Result<Self::Output, Self::Error> {
755        // SAFETY: `ptr` is guaranteed to be valid for 'a and points to an
756        // object of type T
757        unsafe { Ok(Borrowed::from_non_null(py, self.ptr).cast_unchecked()) }
758    }
759}
760
761impl<T: PyClass<Frozen = False>> Drop for PyClassGuardMut<'_, T> {
762    /// Releases the mutable borrow
763    fn drop(&mut self) {
764        self.as_class_object().borrow_checker().release_borrow_mut()
765    }
766}
767
768impl<'a, 'py, T: PyClass<Frozen = False>> TryFrom<&'a Bound<'py, T>> for PyClassGuardMut<'a, T> {
769    type Error = PyBorrowMutError;
770    #[inline]
771    fn try_from(value: &'a Bound<'py, T>) -> Result<Self, Self::Error> {
772        PyClassGuardMut::try_borrow_mut(value.as_unbound())
773    }
774}
775
776// SAFETY: `PyClassGuardMut` only provides access to the inner `T` (and no other
777// Python APIs) which does not require a Python thread state
778#[cfg(feature = "nightly")]
779unsafe impl<T: PyClass<Frozen = False>> crate::marker::Ungil for PyClassGuardMut<'_, T> {}
780// SAFETY: we provide access to
781// - `&T`, which requires `T: Sync` to be Send and `T: Sync` to be Sync
782// - `&mut T`, which requires `T: Send` to be Send and `T: Sync` to be Sync
783unsafe impl<T: PyClass<Frozen = False> + Send + Sync> Send for PyClassGuardMut<'_, T> {}
784unsafe impl<T: PyClass<Frozen = False> + Sync> Sync for PyClassGuardMut<'_, T> {}
785
786/// Custom error type for extracting a [PyClassGuardMut]
787pub struct PyClassGuardMutError<'a, 'py>(pub(crate) Option<CastError<'a, 'py>>);
788
789impl fmt::Debug for PyClassGuardMutError<'_, '_> {
790    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
791        if let Some(e) = &self.0 {
792            write!(f, "{e:?}")
793        } else {
794            write!(f, "{:?}", PyBorrowMutError::new())
795        }
796    }
797}
798
799impl fmt::Display for PyClassGuardMutError<'_, '_> {
800    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
801        if let Some(e) = &self.0 {
802            write!(f, "{e}")
803        } else {
804            write!(f, "{}", PyBorrowMutError::new())
805        }
806    }
807}
808
809impl From<PyClassGuardMutError<'_, '_>> for PyErr {
810    fn from(value: PyClassGuardMutError<'_, '_>) -> Self {
811        if let Some(e) = value.0 {
812            e.into()
813        } else {
814            PyBorrowMutError::new().into()
815        }
816    }
817}
818
819/// Wraps a borrowed reference `U` to a value stored inside of a pyclass `T`
820///
821/// See [`PyClassGuard::map`] and [`PyClassGuardMut::map`]
822pub struct PyClassGuardMap<'a, U: ?Sized, const MUT: bool> {
823    ptr: NonNull<U>,
824    checker: &'a dyn PyClassBorrowChecker,
825}
826
827impl<U: ?Sized, const MUT: bool> Deref for PyClassGuardMap<'_, U, MUT> {
828    type Target = U;
829
830    fn deref(&self) -> &U {
831        // SAFETY: `checker` guards our access to the `T` that `U` points into
832        unsafe { self.ptr.as_ref() }
833    }
834}
835
836impl<U: ?Sized> DerefMut for PyClassGuardMap<'_, U, true> {
837    fn deref_mut(&mut self) -> &mut Self::Target {
838        // SAFETY: `checker` guards our access to the `T` that `U` points into
839        unsafe { self.ptr.as_mut() }
840    }
841}
842
843impl<U: ?Sized, const MUT: bool> Drop for PyClassGuardMap<'_, U, MUT> {
844    fn drop(&mut self) {
845        if MUT {
846            self.checker.release_borrow_mut();
847        } else {
848            self.checker.release_borrow();
849        }
850    }
851}
852
853#[cfg(test)]
854#[cfg(feature = "macros")]
855mod tests {
856    use super::{PyClassGuard, PyClassGuardMut};
857    use crate::{types::PyAnyMethods as _, Bound, IntoPyObject as _, Py, PyErr, Python};
858
859    #[test]
860    fn test_into_frozen_super_released_borrow() {
861        #[crate::pyclass]
862        #[pyo3(crate = "crate", subclass, frozen)]
863        struct BaseClass {}
864
865        #[crate::pyclass]
866        #[pyo3(crate = "crate", extends=BaseClass, subclass)]
867        struct SubClass {}
868
869        #[crate::pymethods]
870        #[pyo3(crate = "crate")]
871        impl SubClass {
872            #[new]
873            fn new(py: Python<'_>) -> Py<SubClass> {
874                let init = crate::PyClassInitializer::from(BaseClass {}).add_subclass(SubClass {});
875                Py::new(py, init).expect("allocation error")
876            }
877        }
878
879        Python::attach(|py| {
880            let obj = SubClass::new(py);
881            drop(PyClassGuard::try_borrow(&obj).unwrap().into_super());
882            assert!(PyClassGuardMut::try_borrow_mut(&obj).is_ok());
883        })
884    }
885
886    #[test]
887    fn test_into_frozen_super_mutable_base_holds_borrow() {
888        #[crate::pyclass]
889        #[pyo3(crate = "crate", subclass)]
890        struct BaseClass {}
891
892        #[crate::pyclass]
893        #[pyo3(crate = "crate", extends=BaseClass, subclass, frozen)]
894        struct SubClass {}
895
896        #[crate::pyclass]
897        #[pyo3(crate = "crate", extends=SubClass, subclass)]
898        struct SubSubClass {}
899
900        #[crate::pymethods]
901        #[pyo3(crate = "crate")]
902        impl SubSubClass {
903            #[new]
904            fn new(py: Python<'_>) -> Py<SubSubClass> {
905                let init = crate::PyClassInitializer::from(BaseClass {})
906                    .add_subclass(SubClass {})
907                    .add_subclass(SubSubClass {});
908                Py::new(py, init).expect("allocation error")
909            }
910        }
911
912        Python::attach(|py| {
913            let obj = SubSubClass::new(py);
914            let _super_borrow = PyClassGuard::try_borrow(&obj).unwrap().into_super();
915            // the whole object still has an immutable borrow, so we cannot
916            // borrow any part mutably (the borrowflag is shared)
917            assert!(PyClassGuardMut::try_borrow_mut(&obj).is_err());
918        })
919    }
920
921    #[crate::pyclass]
922    #[pyo3(crate = "crate", subclass)]
923    struct BaseClass {
924        val1: usize,
925    }
926
927    #[crate::pyclass]
928    #[pyo3(crate = "crate", extends=BaseClass, subclass)]
929    struct SubClass {
930        val2: usize,
931    }
932
933    #[crate::pyclass]
934    #[pyo3(crate = "crate", extends=SubClass)]
935    struct SubSubClass {
936        #[pyo3(get)]
937        val3: usize,
938    }
939
940    #[crate::pymethods]
941    #[pyo3(crate = "crate")]
942    impl SubSubClass {
943        #[new]
944        fn new(py: Python<'_>) -> Py<SubSubClass> {
945            let init = crate::PyClassInitializer::from(BaseClass { val1: 10 })
946                .add_subclass(SubClass { val2: 15 })
947                .add_subclass(SubSubClass { val3: 20 });
948            Py::new(py, init).expect("allocation error")
949        }
950
951        fn get_values(self_: PyClassGuard<'_, Self>) -> (usize, usize, usize) {
952            let val1 = self_.as_super().as_super().val1;
953            let val2 = self_.as_super().val2;
954            (val1, val2, self_.val3)
955        }
956
957        fn double_values(mut self_: PyClassGuardMut<'_, Self>) {
958            self_.as_super().as_super().val1 *= 2;
959            self_.as_super().val2 *= 2;
960            self_.val3 *= 2;
961        }
962
963        fn __add__<'a>(
964            mut slf: PyClassGuardMut<'a, Self>,
965            other: PyClassGuard<'a, Self>,
966        ) -> PyClassGuardMut<'a, Self> {
967            slf.val3 += other.val3;
968            slf
969        }
970
971        fn __rsub__<'a>(
972            slf: PyClassGuard<'a, Self>,
973            mut other: PyClassGuardMut<'a, Self>,
974        ) -> PyClassGuardMut<'a, Self> {
975            other.val3 -= slf.val3;
976            other
977        }
978    }
979
980    #[test]
981    fn test_pyclassguard_into_pyobject() {
982        Python::attach(|py| {
983            let class = Py::new(py, BaseClass { val1: 42 })?;
984            let guard = PyClassGuard::try_borrow(&class).unwrap();
985            let new_ref = (&guard).into_pyobject(py)?;
986            assert!(new_ref.is(&class));
987            let new = guard.into_pyobject(py)?;
988            assert!(new.is(&class));
989            Ok::<_, PyErr>(())
990        })
991        .unwrap();
992    }
993
994    #[test]
995    fn test_pyclassguardmut_into_pyobject() {
996        Python::attach(|py| {
997            let class = Py::new(py, BaseClass { val1: 42 })?;
998            let guard = PyClassGuardMut::try_borrow_mut(&class).unwrap();
999            let new_ref = (&guard).into_pyobject(py)?;
1000            assert!(new_ref.is(&class));
1001            let new = guard.into_pyobject(py)?;
1002            assert!(new.is(&class));
1003            Ok::<_, PyErr>(())
1004        })
1005        .unwrap();
1006    }
1007    #[test]
1008    fn test_pyclassguard_as_super() {
1009        Python::attach(|py| {
1010            let obj = SubSubClass::new(py).into_bound(py);
1011            let pyref = PyClassGuard::try_borrow(obj.as_unbound()).unwrap();
1012            assert_eq!(pyref.as_super().as_super().val1, 10);
1013            assert_eq!(pyref.as_super().val2, 15);
1014            assert_eq!(pyref.val3, 20);
1015            assert_eq!(SubSubClass::get_values(pyref), (10, 15, 20));
1016        });
1017    }
1018
1019    #[test]
1020    fn test_pyclassguardmut_as_super() {
1021        Python::attach(|py| {
1022            let obj = SubSubClass::new(py).into_bound(py);
1023            assert_eq!(
1024                SubSubClass::get_values(PyClassGuard::try_borrow(obj.as_unbound()).unwrap()),
1025                (10, 15, 20)
1026            );
1027            {
1028                let mut pyrefmut = PyClassGuardMut::try_borrow_mut(obj.as_unbound()).unwrap();
1029                assert_eq!(pyrefmut.as_super().as_super().val1, 10);
1030                pyrefmut.as_super().as_super().val1 -= 5;
1031                pyrefmut.as_super().val2 -= 5;
1032                pyrefmut.val3 -= 5;
1033            }
1034            assert_eq!(
1035                SubSubClass::get_values(PyClassGuard::try_borrow(obj.as_unbound()).unwrap()),
1036                (5, 10, 15)
1037            );
1038            SubSubClass::double_values(PyClassGuardMut::try_borrow_mut(obj.as_unbound()).unwrap());
1039            assert_eq!(
1040                SubSubClass::get_values(PyClassGuard::try_borrow(obj.as_unbound()).unwrap()),
1041                (10, 20, 30)
1042            );
1043        });
1044    }
1045
1046    #[test]
1047    fn test_extract_guard() {
1048        Python::attach(|py| {
1049            let obj1 = SubSubClass::new(py);
1050            let obj2 = SubSubClass::new(py);
1051            crate::py_run!(py, obj1 obj2, "assert ((obj1 + obj2) - obj2).val3 == obj1.val3");
1052        });
1053    }
1054
1055    #[test]
1056    fn test_pyclassguards_in_python() {
1057        Python::attach(|py| {
1058            let obj = SubSubClass::new(py);
1059            crate::py_run!(py, obj, "assert obj.get_values() == (10, 15, 20)");
1060            crate::py_run!(py, obj, "assert obj.double_values() is None");
1061            crate::py_run!(py, obj, "assert obj.get_values() == (20, 30, 40)");
1062        });
1063    }
1064
1065    #[crate::pyclass]
1066    #[pyo3(crate = "crate")]
1067    pub struct MyClass {
1068        data: [i32; 100],
1069    }
1070
1071    #[test]
1072    fn test_pyclassguard_map() {
1073        Python::attach(|py| {
1074            let obj = Bound::new(py, MyClass { data: [0; 100] })?;
1075            let data = PyClassGuard::try_borrow(obj.as_unbound())?.map(|c| &c.data);
1076            assert_eq!(data[0], 0);
1077            assert!(obj.try_borrow_mut().is_err()); // obj is still protected
1078            drop(data);
1079            assert!(obj.try_borrow_mut().is_ok()); // drop released shared borrow
1080            Ok::<_, PyErr>(())
1081        })
1082        .unwrap()
1083    }
1084
1085    #[test]
1086    fn test_pyclassguardmut_map() {
1087        Python::attach(|py| {
1088            let obj = Bound::new(py, MyClass { data: [0; 100] })?;
1089            let mut data =
1090                PyClassGuardMut::try_borrow_mut(obj.as_unbound())?.map(|c| c.data.as_mut_slice());
1091            assert_eq!(data[0], 0);
1092            data[0] = 5;
1093            assert_eq!(data[0], 5);
1094            assert!(obj.try_borrow_mut().is_err()); // obj is still protected
1095            drop(data);
1096            assert!(obj.try_borrow_mut().is_ok()); // drop released mutable borrow
1097            Ok::<_, PyErr>(())
1098        })
1099        .unwrap()
1100    }
1101
1102    #[test]
1103    fn test_pyclassguard_map_unrelated() {
1104        use crate::types::{PyString, PyStringMethods};
1105        Python::attach(|py| {
1106            let obj = Bound::new(py, MyClass { data: [0; 100] })?;
1107            let string = PyString::new(py, "pyo3");
1108            // It is possible to return something not borrowing from the guard, but that shouldn't
1109            // matter. `RefCell` has the same behaviour
1110            let refmap = PyClassGuard::try_borrow(obj.as_unbound())?.map(|_| &string);
1111            assert_eq!(refmap.to_cow()?, "pyo3");
1112            Ok::<_, PyErr>(())
1113        })
1114        .unwrap()
1115    }
1116}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here