Skip to content

Commit 8c8c927

Browse files
authored
Merge pull request #147 from kngwyu/unsafe-get
Introduce PyReadonlyArray::get and Make PyArray::get unsafe
2 parents 96c4cae + 69a331c commit 8c8c927

File tree

5 files changed

+112
-74
lines changed

5 files changed

+112
-74
lines changed

CHANGELOG.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
# Changelog
2+
- Unreleased
3+
- `PyArray::get` is now unsafe.
4+
- Introduce `PyArray::get_owned` and `PyReadonlyArray::get`.
5+
26
- v0.10.0
3-
- Remove `ErrorKind` and introduce some concrete error types
7+
- Remove `ErrorKind` and introduce some concrete error types.
48
- `PyArray::as_slice`, `PyArray::as_slice_mut`, `PyArray::as_array`, and `PyArray::as_array_mut` is now unsafe.
5-
- Introduce `PyArray::as_cell_slice`, `PyArray::to_vec`, and `PyArray::to_owned_array`
6-
- Rename `TypeNum` trait `Element`, and `NpyDataType` `DataType`
9+
- Introduce `PyArray::as_cell_slice`, `PyArray::to_vec`, and `PyArray::to_owned_array`.
10+
- Rename `TypeNum` trait `Element`, and `NpyDataType` `DataType`.
711

812
- v0.9.0
913
- Update PyO3 to 0.10.0

src/array.rs

Lines changed: 58 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,19 @@ use crate::types::Element;
7777
/// ```
7878
pub struct PyArray<T, D>(PyAny, PhantomData<T>, PhantomData<D>);
7979

80-
/// one-dimensional array
80+
/// One-dimensional array.
8181
pub type PyArray1<T> = PyArray<T, Ix1>;
82-
/// two-dimensional array
82+
/// Two-dimensional array.
8383
pub type PyArray2<T> = PyArray<T, Ix2>;
84-
/// three-dimensional array
84+
/// Three-dimensional array.
8585
pub type PyArray3<T> = PyArray<T, Ix3>;
86-
/// four-dimensional array
86+
/// Four-dimensional array.
8787
pub type PyArray4<T> = PyArray<T, Ix4>;
88-
/// five-dimensional array
88+
/// Five-dimensional array.
8989
pub type PyArray5<T> = PyArray<T, Ix5>;
90-
/// six-dimensional array
90+
/// Six-dimensional array.
9191
pub type PyArray6<T> = PyArray<T, Ix6>;
92-
/// dynamic-dimensional array
92+
/// Dynamic-dimensional array.
9393
pub type PyArrayDyn<T> = PyArray<T, IxDyn>;
9494

9595
/// Returns a array module.
@@ -117,6 +117,12 @@ impl<'a, T, D> std::convert::From<&'a PyArray<T, D>> for &'a PyAny {
117117
}
118118
}
119119

120+
impl<T, D> IntoPy<PyObject> for PyArray<T, D> {
121+
fn into_py(self, py: Python<'_>) -> PyObject {
122+
unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) }
123+
}
124+
}
125+
120126
impl<'a, T: Element, D: Dimension> FromPyObject<'a> for &'a PyArray<T, D> {
121127
// here we do type-check three times
122128
// 1. Checks if the object is PyArray
@@ -129,14 +135,13 @@ impl<'a, T: Element, D: Dimension> FromPyObject<'a> for &'a PyArray<T, D> {
129135
}
130136
&*(ob as *const PyAny as *const PyArray<T, D>)
131137
};
132-
array.type_check()?;
133-
Ok(array)
134-
}
135-
}
136-
137-
impl<T, D> IntoPy<PyObject> for PyArray<T, D> {
138-
fn into_py(self, py: Python<'_>) -> PyObject {
139-
unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) }
138+
let type_ = unsafe { (*(*array.as_array_ptr()).descr).type_num };
139+
let dim = array.shape().len();
140+
if T::is_same_type(type_) && D::NDIM.map(|n| n == dim).unwrap_or(true) {
141+
Ok(array)
142+
} else {
143+
Err(ShapeError::new(type_, dim, T::DATA_TYPE, D::NDIM).into())
144+
}
140145
}
141146
}
142147

@@ -435,8 +440,10 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
435440
}
436441

437442
/// Returns the immutable view of the internal data of `PyArray` as slice.
438-
/// Please consider the use of [`PyReadonlyArray::as_slice`](../struct.PyReadonlyArray.html)
439-
/// instead of this.
443+
///
444+
/// Please consider the use of safe alternatives
445+
/// ([`PyReadonlyArray::as_slice`](../struct.PyReadonlyArray.html#method.as_slice)
446+
/// , [`as_cell_slice`](#method.as_cell_slice) or [`to_vec`](#method.to_vec)) instead of this.
440447
///
441448
/// # Safety
442449
/// If the internal array is not readonly and can be mutated from Python code,
@@ -491,47 +498,22 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
491498
IntoPyArray::into_pyarray(arr, py)
492499
}
493500

494-
/// Get an immutable reference of a specified element, with checking the passed index is valid.
501+
/// Get the immutable reference of the specified element, with checking the passed index is valid.
495502
///
496-
/// See [NpyIndex](../convert/trait.NpyIndex.html) for what types you can use as index.
497-
///
498-
/// If you pass an invalid index to this function, it returns `None`.
503+
/// Please consider the use of safe alternatives
504+
/// ([`PyReadonlyArray::get`](../struct.PyReadonlyArray.html#method.get)
505+
/// or [`get_owned`](#method.get_owned)) instead of this.
499506
///
500-
/// # Example
501-
/// ```
502-
/// use numpy::PyArray;
503-
/// let gil = pyo3::Python::acquire_gil();
504-
/// let arr = PyArray::arange(gil.python(), 0, 16, 1).reshape([2, 2, 4]).unwrap();
505-
/// assert_eq!(*arr.get([1, 0, 3]).unwrap(), 11);
506-
/// assert!(arr.get([2, 0, 3]).is_none());
507-
/// ```
508-
///
509-
/// For fixed dimension arrays, passing an index with invalid dimension causes compile error.
510-
/// ```compile_fail
511-
/// use numpy::PyArray;
512-
/// let gil = pyo3::Python::acquire_gil();
513-
/// let arr = PyArray::arange(gil.python(), 0, 16, 1).reshape([2, 2, 4]).unwrap();
514-
/// let a = arr.get([1, 2]); // Compile Error!
515-
/// ```
516-
///
517-
/// However, for dinamic arrays, we cannot raise a compile error and just returns `None`.
518-
/// ```
519-
/// use numpy::PyArray;
520-
/// let gil = pyo3::Python::acquire_gil();
521-
/// let arr = PyArray::arange(gil.python(), 0, 16, 1).reshape([2, 2, 4]).unwrap();
522-
/// let arr = arr.to_dyn();
523-
/// assert!(arr.get([1, 2].as_ref()).is_none());
524-
/// ```
507+
/// # Safety
508+
/// If the internal array is not readonly and can be mutated from Python code,
509+
/// holding the slice might cause undefined behavior.
525510
#[inline(always)]
526-
pub fn get<Idx>(&self, index: Idx) -> Option<&T>
527-
where
528-
Idx: NpyIndex<Dim = D>,
529-
{
511+
pub unsafe fn get(&self, index: impl NpyIndex<Dim = D>) -> Option<&T> {
530512
let offset = index.get_checked::<T>(self.shape(), self.strides())?;
531-
unsafe { Some(&*self.data().offset(offset)) }
513+
Some(&*self.data().offset(offset))
532514
}
533515

534-
/// Get an immutable reference of a specified element, without checking the
516+
/// Get the immutable reference of the specified element, without checking the
535517
/// passed index is valid.
536518
///
537519
/// See [NpyIndex](../convert/trait.NpyIndex.html) for what types you can use as index.
@@ -573,14 +555,26 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
573555
unsafe { PyArray::from_borrowed_ptr(python, self.as_ptr()) }
574556
}
575557

576-
fn type_check(&self) -> Result<(), ShapeError> {
577-
let truth = unsafe { (*(*self.as_array_ptr()).descr).type_num };
578-
let dim = self.shape().len();
579-
if T::is_same_type(truth) && D::NDIM.map(|n| n == dim).unwrap_or(true) {
580-
Ok(())
581-
} else {
582-
Err(ShapeError::new(truth, dim, T::DATA_TYPE, D::NDIM))
583-
}
558+
/// Get the copy of the specified element in the array.
559+
///
560+
/// See [NpyIndex](../convert/trait.NpyIndex.html) for what types you can use as index.
561+
///
562+
/// # Example
563+
/// ```
564+
/// use numpy::PyArray2;
565+
/// use pyo3::types::IntoPyDict;
566+
/// let gil = pyo3::Python::acquire_gil();
567+
/// let py = gil.python();
568+
/// let locals = [("np", numpy::get_array_module(py).unwrap())].into_py_dict(py);
569+
/// let array: &PyArray2<i64> = py
570+
/// .eval("np.array([[0, 1], [2, 3]], dtype='int64')", Some(locals), None)
571+
/// .unwrap()
572+
/// .downcast()
573+
/// .unwrap();
574+
/// assert_eq!(array.to_vec().unwrap(), vec![0, 1, 2, 3]);
575+
/// ```
576+
pub fn get_owned(&self, index: impl NpyIndex<Dim = D>) -> Option<T> {
577+
unsafe { self.get(index) }.cloned()
584578
}
585579

586580
/// Returns the copy of the internal data of `PyArray` to `Vec`.
@@ -629,6 +623,10 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
629623
/// Get the immutable view of the internal data of `PyArray`, as
630624
/// [`ndarray::ArrayView`](https://docs.rs/ndarray/latest/ndarray/type.ArrayView.html).
631625
///
626+
/// Please consider the use of safe alternatives
627+
/// ([`PyReadonlyArray::as_array`](../struct.PyReadonlyArray.html#method.as_array)
628+
/// or [`to_array`](#method.to_array)) instead of this.
629+
///
632630
/// # Safety
633631
/// If the internal array is not readonly and can be mutated from Python code,
634632
/// holding the `ArrayView` might cause undefined behavior.

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![allow(clippy::missing_safety_doc, clippy::too_many_arguments)] // FIXME
1+
#![allow(clippy::missing_safety_doc)] // FIXME
22

33
//! `rust-numpy` provides Rust interfaces for [NumPy C APIs](https://numpy.org/doc/stable/reference/c-api),
44
//! especially for [ndarray](https://numpy.org/doc/stable/reference/arrays.ndarray.html) class.

src/npyffi/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Low-Level bindings for NumPy C API.
22
//!
33
//! https://numpy.org/doc/stable/reference/c-api
4-
#![allow(non_camel_case_types)]
4+
#![allow(non_camel_case_types, clippy::too_many_arguments)]
55

66
use pyo3::{ffi, Python};
77
use std::ffi::CString;

src/readonly.rs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Readonly arrays
22
use crate::npyffi::NPY_ARRAY_WRITEABLE;
3-
use crate::{Element, NotContiguousError, PyArray};
3+
use crate::{Element, NotContiguousError, NpyIndex, PyArray};
44
use ndarray::{ArrayView, Dimension, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn};
55
use pyo3::{prelude::*, types::PyAny, AsPyPointer};
66

@@ -27,7 +27,7 @@ use pyo3::{prelude::*, types::PyAny, AsPyPointer};
2727
/// // The internal array is not writeable now.
2828
/// pyo3::py_run!(py, py_array, "assert not py_array.flags['WRITEABLE']");
2929
/// }
30-
/// // After `readonly` drops, the internal array gets writeable again.
30+
/// // After the `readonly` drops, the internal array gets writeable again.
3131
/// pyo3::py_run!(py, py_array, "assert py_array.flags['WRITEABLE']");
3232
/// ```
3333
/// However, if we convert the `PyReadonlyArray` directly into `PyObject`,
@@ -93,21 +93,57 @@ impl<'py, T: Element, D: Dimension> PyReadonlyArray<'py, T, D> {
9393
pub fn as_array(&self) -> ArrayView<'_, T, D> {
9494
unsafe { self.array.as_array() }
9595
}
96+
97+
/// Get an immutable reference of the specified element, with checking the passed index is valid.
98+
///
99+
/// See [NpyIndex](../convert/trait.NpyIndex.html) for what types you can use as index.
100+
///
101+
/// If you pass an invalid index to this function, it returns `None`.
102+
///
103+
/// # Example
104+
/// ```
105+
/// use numpy::PyArray;
106+
/// let gil = pyo3::Python::acquire_gil();
107+
/// let arr = PyArray::arange(gil.python(), 0, 16, 1).reshape([2, 2, 4]).unwrap().readonly();
108+
/// assert_eq!(*arr.get([1, 0, 3]).unwrap(), 11);
109+
/// assert!(arr.get([2, 0, 3]).is_none());
110+
/// ```
111+
///
112+
/// For fixed dimension arrays, passing an index with invalid dimension causes compile error.
113+
/// ```compile_fail
114+
/// use numpy::PyArray;
115+
/// let gil = pyo3::Python::acquire_gil();
116+
/// let arr = PyArray::arange(gil.python(), 0, 16, 1).reshape([2, 2, 4]).unwrap().readonly();
117+
/// let a = arr.get([1, 2]); // Compile Error!
118+
/// ```
119+
///
120+
/// However, for dinamic arrays, we cannot raise a compile error and just returns `None`.
121+
/// ```
122+
/// use numpy::PyArray;
123+
/// let gil = pyo3::Python::acquire_gil();
124+
/// let arr = PyArray::arange(gil.python(), 0, 16, 1).reshape([2, 2, 4]).unwrap();
125+
/// let arr = arr.to_dyn().readonly();
126+
/// assert!(arr.get([1, 2].as_ref()).is_none());
127+
/// ```
128+
#[inline(always)]
129+
pub fn get(&self, index: impl NpyIndex<Dim = D>) -> Option<&T> {
130+
unsafe { self.array.get(index) }
131+
}
96132
}
97133

98-
/// one-dimensional readonly array
134+
/// One-dimensional readonly array.
99135
pub type PyReadonlyArray1<'py, T> = PyReadonlyArray<'py, T, Ix1>;
100-
/// two-dimensional readonly array
136+
/// Two-dimensional readonly array.
101137
pub type PyReadonlyArray2<'py, T> = PyReadonlyArray<'py, T, Ix2>;
102-
/// three-dimensional readonly array
138+
/// Three-dimensional readonly array.
103139
pub type PyReadonlyArray3<'py, T> = PyReadonlyArray<'py, T, Ix3>;
104-
/// four-dimensional readonly array
140+
/// Four-dimensional readonly array.
105141
pub type PyReadonlyArray4<'py, T> = PyReadonlyArray<'py, T, Ix4>;
106-
/// five-dimensional readonly array
142+
/// Five-dimensional readonly array.
107143
pub type PyReadonlyArray5<'py, T> = PyReadonlyArray<'py, T, Ix5>;
108-
/// six-dimensional readonly array
144+
/// Six-dimensional readonly array.
109145
pub type PyReadonlyArray6<'py, T> = PyReadonlyArray<'py, T, Ix6>;
110-
/// dynamic-dimensional readonly array
146+
/// Dynamic-dimensional readonly array.
111147
pub type PyReadonlyArrayDyn<'py, T> = PyReadonlyArray<'py, T, IxDyn>;
112148

113149
impl<'py, T: Element, D: Dimension> FromPyObject<'py> for PyReadonlyArray<'py, T, D> {

0 commit comments

Comments
 (0)