Skip to content

Commit 42843de

Browse files
committed
pyclass methods for Bound
1 parent 8f8d4d3 commit 42843de

File tree

1 file changed

+217
-3
lines changed

1 file changed

+217
-3
lines changed

src/instance.rs

Lines changed: 217 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::err::{self, PyDowncastError, PyErr, PyResult};
2+
use crate::ffi_ptr_ext::FfiPtrExt;
23
use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell};
34
use crate::pyclass::boolean_struct::{False, True};
45
use crate::type_object::HasPyGilRef;
@@ -66,6 +67,43 @@ pub unsafe trait PyNativeType: Sized {
6667
#[repr(transparent)]
6768
pub struct Bound<'py, T>(Python<'py>, ManuallyDrop<Py<T>>);
6869

70+
impl<'py, T> Bound<'py, T>
71+
where
72+
T: PyClass,
73+
{
74+
/// Creates a new instance `Bound<T>` of a `#[pyclass]` on the Python heap.
75+
///
76+
/// # Examples
77+
///
78+
/// ```rust
79+
/// use pyo3::prelude::*;
80+
///
81+
/// #[pyclass]
82+
/// struct Foo {/* fields omitted */}
83+
///
84+
/// # fn main() -> PyResult<()> {
85+
/// Python::with_gil(|py| -> PyResult<Py<Foo>> {
86+
/// let foo: Bound<'_, Foo> = Bound::new(py, Foo {})?;
87+
/// Ok(foo.into())
88+
/// })?;
89+
/// # Ok(())
90+
/// # }
91+
/// ```
92+
pub fn new(
93+
py: Python<'py>,
94+
value: impl Into<PyClassInitializer<T>>,
95+
) -> PyResult<Bound<'py, T>> {
96+
let initializer = value.into();
97+
let obj = initializer.create_cell(py)?;
98+
let ob = unsafe {
99+
obj.cast::<ffi::PyObject>()
100+
.assume_owned(py)
101+
.downcast_into_unchecked()
102+
};
103+
Ok(ob)
104+
}
105+
}
106+
69107
impl<'py> Bound<'py, PyAny> {
70108
/// Constructs a new Bound from a pointer. Panics if ptr is null.
71109
///
@@ -101,6 +139,149 @@ impl<'py> Bound<'py, PyAny> {
101139
}
102140
}
103141

142+
impl<'py, T> Bound<'py, T>
143+
where
144+
T: PyClass,
145+
{
146+
/// Immutably borrows the value `T`.
147+
///
148+
/// This borrow lasts while the returned [`PyRef`] exists.
149+
/// Multiple immutable borrows can be taken out at the same time.
150+
///
151+
/// For frozen classes, the simpler [`get`][Self::get] is available.
152+
///
153+
/// # Examples
154+
///
155+
/// ```rust
156+
/// # use pyo3::prelude::*;
157+
/// #
158+
/// #[pyclass]
159+
/// struct Foo {
160+
/// inner: u8,
161+
/// }
162+
///
163+
/// # fn main() -> PyResult<()> {
164+
/// Python::with_gil(|py| -> PyResult<()> {
165+
/// let foo: Bound<'_, Foo> = Bound::new(py, Foo { inner: 73 })?;
166+
/// let inner: &u8 = &foo.borrow().inner;
167+
///
168+
/// assert_eq!(*inner, 73);
169+
/// Ok(())
170+
/// })?;
171+
/// # Ok(())
172+
/// # }
173+
/// ```
174+
///
175+
/// # Panics
176+
///
177+
/// Panics if the value is currently mutably borrowed. For a non-panicking variant, use
178+
/// [`try_borrow`](#method.try_borrow).
179+
pub fn borrow(&'py self) -> PyRef<'py, T> {
180+
self.get_cell().borrow()
181+
}
182+
183+
/// Mutably borrows the value `T`.
184+
///
185+
/// This borrow lasts while the returned [`PyRefMut`] exists.
186+
///
187+
/// # Examples
188+
///
189+
/// ```
190+
/// # use pyo3::prelude::*;
191+
/// #
192+
/// #[pyclass]
193+
/// struct Foo {
194+
/// inner: u8,
195+
/// }
196+
///
197+
/// # fn main() -> PyResult<()> {
198+
/// Python::with_gil(|py| -> PyResult<()> {
199+
/// let foo: Bound<'_, Foo> = Bound::new(py, Foo { inner: 73 })?;
200+
/// foo.borrow_mut().inner = 35;
201+
///
202+
/// assert_eq!(foo.borrow().inner, 35);
203+
/// Ok(())
204+
/// })?;
205+
/// # Ok(())
206+
/// # }
207+
/// ```
208+
///
209+
/// # Panics
210+
/// Panics if the value is currently borrowed. For a non-panicking variant, use
211+
/// [`try_borrow_mut`](#method.try_borrow_mut).
212+
pub fn borrow_mut(&'py self) -> PyRefMut<'py, T>
213+
where
214+
T: PyClass<Frozen = False>,
215+
{
216+
self.get_cell().borrow_mut()
217+
}
218+
219+
/// Attempts to immutably borrow the value `T`, returning an error if the value is currently mutably borrowed.
220+
///
221+
/// The borrow lasts while the returned [`PyRef`] exists.
222+
///
223+
/// This is the non-panicking variant of [`borrow`](#method.borrow).
224+
///
225+
/// For frozen classes, the simpler [`get`][Self::get] is available.
226+
pub fn try_borrow(&'py self) -> Result<PyRef<'py, T>, PyBorrowError> {
227+
self.get_cell().try_borrow()
228+
}
229+
230+
/// Attempts to mutably borrow the value `T`, returning an error if the value is currently borrowed.
231+
///
232+
/// The borrow lasts while the returned [`PyRefMut`] exists.
233+
///
234+
/// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut).
235+
pub fn try_borrow_mut(&'py self) -> Result<PyRefMut<'py, T>, PyBorrowMutError>
236+
where
237+
T: PyClass<Frozen = False>,
238+
{
239+
self.get_cell().try_borrow_mut()
240+
}
241+
242+
/// Provide an immutable borrow of the value `T` without acquiring the GIL.
243+
///
244+
/// This is available if the class is [`frozen`][macro@crate::pyclass] and [`Sync`].
245+
///
246+
/// # Examples
247+
///
248+
/// ```
249+
/// use std::sync::atomic::{AtomicUsize, Ordering};
250+
/// # use pyo3::prelude::*;
251+
///
252+
/// #[pyclass(frozen)]
253+
/// struct FrozenCounter {
254+
/// value: AtomicUsize,
255+
/// }
256+
///
257+
/// Python::with_gil(|py| {
258+
/// let counter = FrozenCounter { value: AtomicUsize::new(0) };
259+
///
260+
/// let py_counter = Bound::new(py, counter).unwrap();
261+
///
262+
/// py_counter.get().value.fetch_add(1, Ordering::Relaxed);
263+
/// });
264+
/// ```
265+
pub fn get(&self) -> &T
266+
where
267+
T: PyClass<Frozen = True> + Sync,
268+
{
269+
let cell = self.get_cell();
270+
// SAFETY: The class itself is frozen and `Sync` and we do not access anything but `cell.contents.value`.
271+
unsafe { &*cell.get_ptr() }
272+
}
273+
274+
fn get_cell(&'py self) -> &'py PyCell<T> {
275+
let cell = self.as_ptr().cast::<PyCell<T>>();
276+
// SAFETY: Bound<T> is known to contain an object which is laid out in memory as a
277+
// PyCell<T>.
278+
//
279+
// Strictly speaking for now `&'py PyCell<T>` is part of the "GIL Ref" API, so this
280+
// could use some further refactoring later to avoid going through this reference.
281+
unsafe { &*cell }
282+
}
283+
}
284+
104285
impl<'py, T> std::fmt::Debug for Bound<'py, T> {
105286
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
106287
let any = self.as_any();
@@ -1757,12 +1938,11 @@ a = A()
17571938

17581939
use super::*;
17591940

1760-
#[crate::pyclass]
1761-
#[pyo3(crate = "crate")]
1941+
#[crate::pyclass(crate = "crate")]
17621942
struct SomeClass(i32);
17631943

17641944
#[test]
1765-
fn instance_borrow_methods() {
1945+
fn py_borrow_methods() {
17661946
// More detailed tests of the underlying semantics in pycell.rs
17671947
Python::with_gil(|py| {
17681948
let instance = Py::new(py, SomeClass(0)).unwrap();
@@ -1780,6 +1960,40 @@ a = A()
17801960
})
17811961
}
17821962

1963+
#[test]
1964+
fn bound_borrow_methods() {
1965+
// More detailed tests of the underlying semantics in pycell.rs
1966+
Python::with_gil(|py| {
1967+
let instance = Bound::new(py, SomeClass(0)).unwrap();
1968+
assert_eq!(instance.borrow().0, 0);
1969+
assert_eq!(instance.try_borrow().unwrap().0, 0);
1970+
assert_eq!(instance.borrow_mut().0, 0);
1971+
assert_eq!(instance.try_borrow_mut().unwrap().0, 0);
1972+
1973+
instance.borrow_mut().0 = 123;
1974+
1975+
assert_eq!(instance.borrow().0, 123);
1976+
assert_eq!(instance.try_borrow().unwrap().0, 123);
1977+
assert_eq!(instance.borrow_mut().0, 123);
1978+
assert_eq!(instance.try_borrow_mut().unwrap().0, 123);
1979+
})
1980+
}
1981+
1982+
#[crate::pyclass(frozen, crate = "crate")]
1983+
struct FrozenClass(i32);
1984+
1985+
#[test]
1986+
fn test_frozen_get() {
1987+
Python::with_gil(|py| {
1988+
for i in 0..10 {
1989+
let instance = Py::new(py, FrozenClass(i)).unwrap();
1990+
assert_eq!(instance.get().0, i);
1991+
1992+
assert_eq!(instance.bind(py).get().0, i);
1993+
}
1994+
})
1995+
}
1996+
17831997
#[test]
17841998
#[allow(deprecated)]
17851999
fn cell_tryfrom() {

0 commit comments

Comments
 (0)