Skip to content

Commit 700b4cc

Browse files
authored
add PyClassGuard(Mut)::map (#5311)
1 parent 4e24c89 commit 700b4cc

File tree

3 files changed

+155
-3
lines changed

3 files changed

+155
-3
lines changed

src/pycell/impl_.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ pub struct BorrowChecker(BorrowFlag);
9898

9999
pub trait PyClassBorrowChecker {
100100
/// Initial value for self
101-
fn new() -> Self;
101+
fn new() -> Self
102+
where
103+
Self: Sized;
102104

103105
/// Increments immutable borrow count, if possible
104106
fn try_borrow(&self) -> Result<(), PyBorrowError>;

src/pyclass.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mod guard;
99
pub(crate) use self::create_type_object::{create_type_object, PyClassTypeObject};
1010

1111
pub use self::gc::{PyTraverseError, PyVisit};
12-
pub use self::guard::{PyClassGuard, PyClassGuardMut};
12+
pub use self::guard::{PyClassGuard, PyClassGuardMap, PyClassGuardMut};
1313

1414
/// Types that can be used as Python classes.
1515
///

src/pyclass/guard.rs

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,38 @@ impl<'a, T: PyClass> PyClassGuard<'a, T> {
106106
// valid for at least 'a
107107
unsafe { self.ptr.cast().as_ref() }
108108
}
109+
110+
/// Consumes the [`PyClassGuard`] and returns a [`PyClassGuardMap`] for a component of the
111+
/// borrowed data
112+
///
113+
/// # Examples
114+
///
115+
/// ```
116+
/// # use pyo3::prelude::*;
117+
/// # use pyo3::PyClassGuard;
118+
///
119+
/// #[pyclass]
120+
/// pub struct MyClass {
121+
/// msg: String,
122+
/// }
123+
///
124+
/// # Python::attach(|py| {
125+
/// let obj = Bound::new(py, MyClass { msg: String::from("hello") })?;
126+
/// let msg = obj.extract::<PyClassGuard<'_, MyClass>>()?.map(|c| &c.msg);
127+
/// assert_eq!(&*msg, "hello");
128+
/// # Ok::<_, PyErr>(())
129+
/// # }).unwrap();
130+
/// ```
131+
pub fn map<F, U: ?Sized>(self, f: F) -> PyClassGuardMap<'a, U, false>
132+
where
133+
F: FnOnce(&T) -> &U,
134+
{
135+
let slf = std::mem::ManuallyDrop::new(self); // the borrow is released when dropping the `PyClassGuardMap`
136+
PyClassGuardMap {
137+
ptr: NonNull::from(f(&slf)),
138+
checker: slf.as_class_object().borrow_checker(),
139+
}
140+
}
109141
}
110142

111143
impl<'a, T, U> PyClassGuard<'a, T>
@@ -521,6 +553,38 @@ impl<'a, T: PyClass<Frozen = False>> PyClassGuardMut<'a, T> {
521553
// valid for at least 'a
522554
unsafe { self.ptr.cast().as_ref() }
523555
}
556+
557+
/// Consumes the [`PyClassGuardMut`] and returns a [`PyClassGuardMap`] for a component of the
558+
/// borrowed data
559+
///
560+
/// # Examples
561+
///
562+
/// ```
563+
/// # use pyo3::prelude::*;
564+
/// # use pyo3::PyClassGuardMut;
565+
///
566+
/// #[pyclass]
567+
/// pub struct MyClass {
568+
/// data: [i32; 100],
569+
/// }
570+
///
571+
/// # Python::attach(|py| {
572+
/// let obj = Bound::new(py, MyClass { data: [0; 100] })?;
573+
/// let mut data = obj.extract::<PyClassGuardMut<'_, MyClass>>()?.map(|c| c.data.as_mut_slice());
574+
/// data[0] = 42;
575+
/// # Ok::<_, PyErr>(())
576+
/// # }).unwrap();
577+
/// ```
578+
pub fn map<F, U: ?Sized>(self, f: F) -> PyClassGuardMap<'a, U, true>
579+
where
580+
F: FnOnce(&mut T) -> &mut U,
581+
{
582+
let mut slf = std::mem::ManuallyDrop::new(self); // the borrow is released when dropping the `PyClassGuardMap`
583+
PyClassGuardMap {
584+
ptr: NonNull::from(f(&mut slf)),
585+
checker: slf.as_class_object().borrow_checker(),
586+
}
587+
}
524588
}
525589

526590
impl<'a, T, U> PyClassGuardMut<'a, T>
@@ -620,11 +684,45 @@ unsafe impl<T: PyClass<Frozen = False>> crate::marker::Ungil for PyClassGuardMut
620684
unsafe impl<T: PyClass<Frozen = False> + Send + Sync> Send for PyClassGuardMut<'_, T> {}
621685
unsafe impl<T: PyClass<Frozen = False> + Sync> Sync for PyClassGuardMut<'_, T> {}
622686

687+
/// Wraps a borrowed reference `U` to a value stored inside of a pyclass `T`
688+
///
689+
/// See [`PyClassGuard::map`] and [`PyClassGuardMut::map`]
690+
pub struct PyClassGuardMap<'a, U: ?Sized, const MUT: bool> {
691+
ptr: NonNull<U>,
692+
checker: &'a dyn PyClassBorrowChecker,
693+
}
694+
695+
impl<U: ?Sized, const MUT: bool> Deref for PyClassGuardMap<'_, U, MUT> {
696+
type Target = U;
697+
698+
fn deref(&self) -> &U {
699+
// SAFETY: `checker` guards our access to the `T` that `U` points into
700+
unsafe { self.ptr.as_ref() }
701+
}
702+
}
703+
704+
impl<U: ?Sized> DerefMut for PyClassGuardMap<'_, U, true> {
705+
fn deref_mut(&mut self) -> &mut Self::Target {
706+
// SAFETY: `checker` guards our access to the `T` that `U` points into
707+
unsafe { self.ptr.as_mut() }
708+
}
709+
}
710+
711+
impl<U: ?Sized, const MUT: bool> Drop for PyClassGuardMap<'_, U, MUT> {
712+
fn drop(&mut self) {
713+
if MUT {
714+
self.checker.release_borrow_mut();
715+
} else {
716+
self.checker.release_borrow();
717+
}
718+
}
719+
}
720+
623721
#[cfg(test)]
624722
#[cfg(feature = "macros")]
625723
mod tests {
626724
use super::{PyClassGuard, PyClassGuardMut};
627-
use crate::{types::PyAnyMethods as _, IntoPyObject as _, Py, PyErr, Python};
725+
use crate::{types::PyAnyMethods as _, Bound, IntoPyObject as _, Py, PyErr, Python};
628726

629727
#[test]
630728
fn test_into_frozen_super_released_borrow() {
@@ -831,4 +929,56 @@ mod tests {
831929
crate::py_run!(py, obj, "assert obj.get_values() == (20, 30, 40)");
832930
});
833931
}
932+
933+
#[crate::pyclass]
934+
#[pyo3(crate = "crate")]
935+
pub struct MyClass {
936+
data: [i32; 100],
937+
}
938+
939+
#[test]
940+
fn test_pyclassguard_map() {
941+
Python::attach(|py| {
942+
let obj = Bound::new(py, MyClass { data: [0; 100] })?;
943+
let data = PyClassGuard::try_borrow(obj.as_unbound())?.map(|c| &c.data);
944+
assert_eq!(data[0], 0);
945+
assert!(obj.try_borrow_mut().is_err()); // obj is still protected
946+
drop(data);
947+
assert!(obj.try_borrow_mut().is_ok()); // drop released shared borrow
948+
Ok::<_, PyErr>(())
949+
})
950+
.unwrap()
951+
}
952+
953+
#[test]
954+
fn test_pyclassguardmut_map() {
955+
Python::attach(|py| {
956+
let obj = Bound::new(py, MyClass { data: [0; 100] })?;
957+
let mut data =
958+
PyClassGuardMut::try_borrow_mut(obj.as_unbound())?.map(|c| c.data.as_mut_slice());
959+
assert_eq!(data[0], 0);
960+
data[0] = 5;
961+
assert_eq!(data[0], 5);
962+
assert!(obj.try_borrow_mut().is_err()); // obj is still protected
963+
drop(data);
964+
assert!(obj.try_borrow_mut().is_ok()); // drop released mutable borrow
965+
Ok::<_, PyErr>(())
966+
})
967+
.unwrap()
968+
}
969+
970+
#[test]
971+
fn test_pyclassguard_map_unrelated() {
972+
use crate::types::{PyString, PyStringMethods};
973+
Python::attach(|py| {
974+
let obj = Bound::new(py, MyClass { data: [0; 100] })?;
975+
let string = PyString::new(py, "pyo3");
976+
// It is possible to return something not borrowing from the guard, but that shouldn't
977+
// matter. `RefCell` has the same behaviour
978+
let refmap = PyClassGuard::try_borrow(obj.as_unbound())?.map(|_| &string);
979+
assert_eq!(refmap.to_cow()?, "pyo3");
980+
Ok::<_, PyErr>(())
981+
})
982+
.unwrap()
983+
}
834984
}

0 commit comments

Comments
 (0)