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}