Skip to main content

pyo3/pyclass/
guard.rs

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