@@ -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
111143impl < ' 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
526590impl < ' a , T , U > PyClassGuardMut < ' a , T >
@@ -620,11 +684,45 @@ unsafe impl<T: PyClass<Frozen = False>> crate::marker::Ungil for PyClassGuardMut
620684unsafe impl < T : PyClass < Frozen = False > + Send + Sync > Send for PyClassGuardMut < ' _ , T > { }
621685unsafe 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" ) ]
625723mod 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