diff --git a/CHANGELOG.md b/CHANGELOG.md index 97707dc45..a5815358b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - v0.28.0 - Fix mismatched behavior between `PyArrayLike1` and `PyArrayLike2` when used with floats ([#520](https://github.com/PyO3/rust-numpy/pull/520)) + - Add ownership-moving conversions into `PyReadonlyArray` and `PyReadwriteArray` ([#524](https://github.com/PyO3/rust-numpy/pull/524)) - v0.27.1 - Bump ndarray dependency to v0.17. ([#516](https://github.com/PyO3/rust-numpy/pull/516)) diff --git a/src/array.rs b/src/array.rs index 8ab548653..737101e9a 100644 --- a/src/array.rs +++ b/src/array.rs @@ -714,7 +714,7 @@ unsafe fn clone_elements(py: Python<'_>, elems: &[T], data_ptr: &mut /// Implementation of functionality for [`PyArray`]. #[doc(alias = "PyArray")] -pub trait PyArrayMethods<'py, T, D>: PyUntypedArrayMethods<'py> { +pub trait PyArrayMethods<'py, T, D>: PyUntypedArrayMethods<'py> + Sized { /// Access an untyped representation of this array. fn as_untyped(&self) -> &Bound<'py, PyUntypedArray>; @@ -956,12 +956,33 @@ pub trait PyArrayMethods<'py, T, D>: PyUntypedArrayMethods<'py> { T: Element, D: Dimension; + /// Consume `self` into an immutable borrow of the NumPy array + fn try_into_readonly(self) -> Result, BorrowError> + where + T: Element, + D: Dimension; + /// Get an immutable borrow of the NumPy array fn try_readonly(&self) -> Result, BorrowError> where T: Element, D: Dimension; + /// Consume `self` into an immutable borrow of the NumPy array + /// + /// # Panics + /// + /// Panics if the allocation backing the array is currently mutably borrowed. + /// + /// For a non-panicking variant, use [`try_into_readonly`][Self::try_into_readonly]. + fn into_readonly(self) -> PyReadonlyArray<'py, T, D> + where + T: Element, + D: Dimension, + { + self.try_into_readonly().unwrap() + } + /// Get an immutable borrow of the NumPy array /// /// # Panics @@ -977,12 +998,36 @@ pub trait PyArrayMethods<'py, T, D>: PyUntypedArrayMethods<'py> { self.try_readonly().unwrap() } + /// Consume `self` into an mutable borrow of the NumPy array + fn try_into_readwrite(self) -> Result, BorrowError> + where + T: Element, + D: Dimension; + /// Get a mutable borrow of the NumPy array fn try_readwrite(&self) -> Result, BorrowError> where T: Element, D: Dimension; + /// Consume `self` into an mutable borrow of the NumPy array + /// + /// # Panics + /// + /// Panics if the allocation backing the array is currently borrowed or + /// if the array is [flagged as][flags] not writeable. + /// + /// For a non-panicking variant, use [`try_into_readwrite`][Self::try_into_readwrite]. + /// + /// [flags]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html + fn into_readwrite(self) -> PyReadwriteArray<'py, T, D> + where + T: Element, + D: Dimension, + { + self.try_into_readwrite().unwrap() + } + /// Get a mutable borrow of the NumPy array /// /// # Panics @@ -1467,12 +1512,28 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray> { slice.map(|slc| T::vec_from_slice(self.py(), slc)) } + fn try_into_readonly(self) -> Result, BorrowError> + where + T: Element, + D: Dimension, + { + PyReadonlyArray::try_new(self) + } + fn try_readonly(&self) -> Result, BorrowError> where T: Element, D: Dimension, { - PyReadonlyArray::try_new(self.clone()) + self.clone().try_into_readonly() + } + + fn try_into_readwrite(self) -> Result, BorrowError> + where + T: Element, + D: Dimension, + { + PyReadwriteArray::try_new(self) } fn try_readwrite(&self) -> Result, BorrowError> @@ -1480,7 +1541,7 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray> { T: Element, D: Dimension, { - PyReadwriteArray::try_new(self.clone()) + self.clone().try_into_readwrite() } unsafe fn as_array(&self) -> ArrayView<'_, T, D> diff --git a/src/borrow/mod.rs b/src/borrow/mod.rs index 86d7110f9..c08b74017 100644 --- a/src/borrow/mod.rs +++ b/src/borrow/mod.rs @@ -606,6 +606,13 @@ where /// /// Safe wrapper for [`PyArray::resize`]. /// + /// Note that as this mutates a pointed-to object, the [`PyReadwriteArray`] must be the only + /// Python reference to the object. There cannot be `PyArray` pointers or even `Bound` + /// pointing to the same object; this means for example that an object received from a PyO3 + /// `pyfunction` cannot call this method, since the PyO3 wrapper maintains a reference itself. + /// Attempting to call this method when there are other Python references is still safe; NumPy + /// will raise a Python-space exception. + /// /// # Example /// /// ``` @@ -616,7 +623,7 @@ where /// let pyarray = PyArray::arange(py, 0, 10, 1); /// assert_eq!(pyarray.len(), 10); /// - /// let pyarray = pyarray.readwrite(); + /// let pyarray = pyarray.into_readwrite(); /// let pyarray = pyarray.resize(100).unwrap(); /// assert_eq!(pyarray.len(), 100); /// }); @@ -722,7 +729,7 @@ mod tests { .cast_into::>() .unwrap(); - let exclusive = array.readwrite(); + let exclusive = array.into_readwrite(); assert!(exclusive.resize(100).is_err()); }); } @@ -732,7 +739,7 @@ mod tests { Python::attach(|py| { let array = PyArray::::zeros(py, 10, false); - let exclusive = array.readwrite(); + let exclusive = array.into_readwrite(); assert!(exclusive.resize(10).is_ok()); }); } diff --git a/tests/borrow.rs b/tests/borrow.rs index 49bf59d61..52c1ad8fb 100644 --- a/tests/borrow.rs +++ b/tests/borrow.rs @@ -31,7 +31,7 @@ fn multiple_shared_borrows() { let array = PyArray::::zeros(py, (1, 2, 3), false); let shared1 = array.readonly(); - let shared2 = array.readonly(); + let shared2 = array.into_readonly(); assert_eq!(shared2.shape(), [1, 2, 3]); assert_eq!(shared1.shape(), [1, 2, 3]); @@ -341,7 +341,7 @@ fn resize_using_exclusive_borrow() { let array = PyArray::::zeros(py, 3, false); assert_eq!(array.shape(), [3]); - let mut array = array.readwrite(); + let mut array = array.into_readwrite(); assert_eq!(array.as_slice_mut().unwrap(), &[0.0; 3]); let mut array = array.resize(5).unwrap();