diff --git a/src/instance.rs b/src/instance.rs index d21cc307..8ae6d80f 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,4 +1,5 @@ use crate::err::{self, PyDowncastError, PyErr, PyResult}; +use crate::ffi_ptr_ext::FfiPtrExt; use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell}; use crate::pyclass::boolean_struct::{False, True}; use crate::type_object::HasPyGilRef; @@ -66,6 +67,43 @@ pub unsafe trait PyNativeType: Sized { #[repr(transparent)] pub struct Bound<'py, T>(Python<'py>, ManuallyDrop>); +impl<'py, T> Bound<'py, T> +where + T: PyClass, +{ + /// Creates a new instance `Bound` of a `#[pyclass]` on the Python heap. + /// + /// # Examples + /// + /// ```rust + /// use pyo3::prelude::*; + /// + /// #[pyclass] + /// struct Foo {/* fields omitted */} + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| -> PyResult> { + /// let foo: Bound<'_, Foo> = Bound::new(py, Foo {})?; + /// Ok(foo.into()) + /// })?; + /// # Ok(()) + /// # } + /// ``` + pub fn new( + py: Python<'py>, + value: impl Into>, + ) -> PyResult> { + let initializer = value.into(); + let obj = initializer.create_cell(py)?; + let ob = unsafe { + obj.cast::() + .assume_owned(py) + .downcast_into_unchecked() + }; + Ok(ob) + } +} + impl<'py> Bound<'py, PyAny> { /// Constructs a new Bound from a pointer. Panics if ptr is null. /// @@ -101,6 +139,149 @@ impl<'py> Bound<'py, PyAny> { } } +impl<'py, T> Bound<'py, T> +where + T: PyClass, +{ + /// Immutably borrows the value `T`. + /// + /// This borrow lasts while the returned [`PyRef`] exists. + /// Multiple immutable borrows can be taken out at the same time. + /// + /// For frozen classes, the simpler [`get`][Self::get] is available. + /// + /// # Examples + /// + /// ```rust + /// # use pyo3::prelude::*; + /// # + /// #[pyclass] + /// struct Foo { + /// inner: u8, + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| -> PyResult<()> { + /// let foo: Bound<'_, Foo> = Bound::new(py, Foo { inner: 73 })?; + /// let inner: &u8 = &foo.borrow().inner; + /// + /// assert_eq!(*inner, 73); + /// Ok(()) + /// })?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Panics + /// + /// Panics if the value is currently mutably borrowed. For a non-panicking variant, use + /// [`try_borrow`](#method.try_borrow). + pub fn borrow(&'py self) -> PyRef<'py, T> { + self.get_cell().borrow() + } + + /// Mutably borrows the value `T`. + /// + /// This borrow lasts while the returned [`PyRefMut`] exists. + /// + /// # Examples + /// + /// ``` + /// # use pyo3::prelude::*; + /// # + /// #[pyclass] + /// struct Foo { + /// inner: u8, + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| -> PyResult<()> { + /// let foo: Bound<'_, Foo> = Bound::new(py, Foo { inner: 73 })?; + /// foo.borrow_mut().inner = 35; + /// + /// assert_eq!(foo.borrow().inner, 35); + /// Ok(()) + /// })?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Panics + /// Panics if the value is currently borrowed. For a non-panicking variant, use + /// [`try_borrow_mut`](#method.try_borrow_mut). + pub fn borrow_mut(&'py self) -> PyRefMut<'py, T> + where + T: PyClass, + { + self.get_cell().borrow_mut() + } + + /// Attempts to immutably borrow the value `T`, returning an error if the value is currently mutably borrowed. + /// + /// The borrow lasts while the returned [`PyRef`] exists. + /// + /// This is the non-panicking variant of [`borrow`](#method.borrow). + /// + /// For frozen classes, the simpler [`get`][Self::get] is available. + pub fn try_borrow(&'py self) -> Result, PyBorrowError> { + self.get_cell().try_borrow() + } + + /// Attempts to mutably borrow the value `T`, returning an error if the value is currently borrowed. + /// + /// The borrow lasts while the returned [`PyRefMut`] exists. + /// + /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut). + pub fn try_borrow_mut(&'py self) -> Result, PyBorrowMutError> + where + T: PyClass, + { + self.get_cell().try_borrow_mut() + } + + /// Provide an immutable borrow of the value `T` without acquiring the GIL. + /// + /// This is available if the class is [`frozen`][macro@crate::pyclass] and [`Sync`]. + /// + /// # Examples + /// + /// ``` + /// use std::sync::atomic::{AtomicUsize, Ordering}; + /// # use pyo3::prelude::*; + /// + /// #[pyclass(frozen)] + /// struct FrozenCounter { + /// value: AtomicUsize, + /// } + /// + /// Python::with_gil(|py| { + /// let counter = FrozenCounter { value: AtomicUsize::new(0) }; + /// + /// let py_counter = Bound::new(py, counter).unwrap(); + /// + /// py_counter.get().value.fetch_add(1, Ordering::Relaxed); + /// }); + /// ``` + pub fn get(&self) -> &T + where + T: PyClass + Sync, + { + let cell = self.get_cell(); + // SAFETY: The class itself is frozen and `Sync` and we do not access anything but `cell.contents.value`. + unsafe { &*cell.get_ptr() } + } + + fn get_cell(&'py self) -> &'py PyCell { + let cell = self.as_ptr().cast::>(); + // SAFETY: Bound is known to contain an object which is laid out in memory as a + // PyCell. + // + // Strictly speaking for now `&'py PyCell` is part of the "GIL Ref" API, so this + // could use some further refactoring later to avoid going through this reference. + unsafe { &*cell } + } +} + impl<'py, T> std::fmt::Debug for Bound<'py, T> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { let any = self.as_any(); @@ -1768,12 +1949,11 @@ a = A() use super::*; - #[crate::pyclass] - #[pyo3(crate = "crate")] + #[crate::pyclass(crate = "crate")] struct SomeClass(i32); #[test] - fn instance_borrow_methods() { + fn py_borrow_methods() { // More detailed tests of the underlying semantics in pycell.rs Python::with_gil(|py| { let instance = Py::new(py, SomeClass(0)).unwrap(); @@ -1791,6 +1971,40 @@ a = A() }) } + #[test] + fn bound_borrow_methods() { + // More detailed tests of the underlying semantics in pycell.rs + Python::with_gil(|py| { + let instance = Bound::new(py, SomeClass(0)).unwrap(); + assert_eq!(instance.borrow().0, 0); + assert_eq!(instance.try_borrow().unwrap().0, 0); + assert_eq!(instance.borrow_mut().0, 0); + assert_eq!(instance.try_borrow_mut().unwrap().0, 0); + + instance.borrow_mut().0 = 123; + + assert_eq!(instance.borrow().0, 123); + assert_eq!(instance.try_borrow().unwrap().0, 123); + assert_eq!(instance.borrow_mut().0, 123); + assert_eq!(instance.try_borrow_mut().unwrap().0, 123); + }) + } + + #[crate::pyclass(frozen, crate = "crate")] + struct FrozenClass(i32); + + #[test] + fn test_frozen_get() { + Python::with_gil(|py| { + for i in 0..10 { + let instance = Py::new(py, FrozenClass(i)).unwrap(); + assert_eq!(instance.get().0, i); + + assert_eq!(instance.bind(py).get().0, i); + } + }) + } + #[test] #[allow(deprecated)] fn cell_tryfrom() {