diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f36491055..0ff59bf4c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -232,7 +232,7 @@ jobs: with: python-version: "3.14" - name: Install Rust - uses: dtolnay/rust-toolchain@1.83 + uses: dtolnay/rust-toolchain@1.85 - uses: Swatinem/rust-cache@v2 with: workspaces: examples/simple diff --git a/Cargo.toml b/Cargo.toml index cca003566..e5474583a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ authors = [ ] description = "PyO3-based Rust bindings of the NumPy C-API" documentation = "https://docs.rs/numpy" -edition = "2021" -rust-version = "1.83" +edition = "2024" +rust-version = "1.85" repository = "https://github.com/PyO3/rust-numpy" categories = ["api-bindings", "development-tools::ffi", "science"] keywords = ["python", "numpy", "ffi", "pyo3"] @@ -58,7 +58,7 @@ all-features = true [lints.rust] # We usually want to make the GIL lifetime explicit. -elided-lifetimes-in-paths = "deny" +elided_lifetimes_in_paths = "deny" [lints.clippy] -needless-lifetimes = "allow" +needless_lifetimes = "allow" diff --git a/benches/array.rs b/benches/array.rs index 8d6693499..36c444848 100644 --- a/benches/array.rs +++ b/benches/array.rs @@ -1,8 +1,8 @@ use std::{hint::black_box, ops::Range}; -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use numpy::{PyArray1, PyArray2, PyArray3}; -use pyo3::{types::PyAnyMethods, Bound, IntoPyObjectExt, Python}; +use pyo3::{Bound, IntoPyObjectExt, Python, types::PyAnyMethods}; fn extract_success(c: &mut Criterion) { Python::attach(|py| { diff --git a/benches/array_like.rs b/benches/array_like.rs index 033783bb1..a74188334 100644 --- a/benches/array_like.rs +++ b/benches/array_like.rs @@ -1,10 +1,10 @@ use std::hint::black_box; -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use numpy::{PyArrayLike1, PyArrayLike2, PyArrayLike3}; use pyo3::{ - types::{PyAnyMethods, PyDict}, Python, + types::{PyAnyMethods, PyDict}, }; fn extract_array_like_1(c: &mut Criterion) { diff --git a/benches/borrow.rs b/benches/borrow.rs index fa425db64..78baabf64 100644 --- a/benches/borrow.rs +++ b/benches/borrow.rs @@ -1,6 +1,6 @@ use std::hint::black_box; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use numpy::{PyArray, PyArrayMethods}; use pyo3::Python; diff --git a/examples/linalg/Cargo.toml b/examples/linalg/Cargo.toml index 72c8fbb8c..bdb926b1e 100644 --- a/examples/linalg/Cargo.toml +++ b/examples/linalg/Cargo.toml @@ -2,7 +2,7 @@ name = "rust-linalg" version = "0.1.0" authors = ["Yuji Kanagawa "] -edition = "2021" +edition = "2024" [lib] crate-type = ["cdylib"] diff --git a/examples/linalg/src/lib.rs b/examples/linalg/src/lib.rs index d5c91e04c..a4ab1d101 100755 --- a/examples/linalg/src/lib.rs +++ b/examples/linalg/src/lib.rs @@ -2,7 +2,7 @@ mod rust_linalg { use ndarray_linalg::solve::Inverse; use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2}; - use pyo3::{exceptions::PyRuntimeError, pyfunction, Bound, PyResult, Python}; + use pyo3::{Bound, PyResult, Python, exceptions::PyRuntimeError, pyfunction}; #[pyfunction] fn inv<'py>( diff --git a/examples/parallel/Cargo.toml b/examples/parallel/Cargo.toml index e602ee986..070e42426 100644 --- a/examples/parallel/Cargo.toml +++ b/examples/parallel/Cargo.toml @@ -2,7 +2,7 @@ name = "rust-parallel" version = "0.1.0" authors = ["Yuji Kanagawa "] -edition = "2021" +edition = "2024" [lib] crate-type = ["cdylib"] diff --git a/examples/parallel/src/lib.rs b/examples/parallel/src/lib.rs index 1a7c327b7..961dd4ce5 100755 --- a/examples/parallel/src/lib.rs +++ b/examples/parallel/src/lib.rs @@ -5,7 +5,7 @@ extern crate blas_src; mod rust_parallel { use numpy::ndarray::Zip; use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1, PyReadonlyArray2}; - use pyo3::{pyfunction, Bound, Python}; + use pyo3::{Bound, Python, pyfunction}; #[pyfunction] fn rows_dot<'py>( diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml index 2bbf5fad0..8b6b7b97f 100644 --- a/examples/simple/Cargo.toml +++ b/examples/simple/Cargo.toml @@ -2,7 +2,7 @@ name = "rust-ext" version = "0.1.0" authors = ["Toshiki Teramura ", "Yuji Kanagawa "] -edition = "2021" +edition = "2024" [lib] crate-type = ["cdylib"] diff --git a/examples/simple/src/lib.rs b/examples/simple/src/lib.rs index 9f096e70f..257412ae3 100644 --- a/examples/simple/src/lib.rs +++ b/examples/simple/src/lib.rs @@ -2,15 +2,15 @@ mod rust_ext { use numpy::ndarray::{Array1, ArrayD, ArrayView1, ArrayViewD, ArrayViewMutD, Zip}; use numpy::{ - datetime::{units, Timedelta}, Complex64, IntoPyArray, PyArray1, PyArrayDyn, PyArrayMethods, PyReadonlyArray1, PyReadonlyArrayDyn, PyReadwriteArray1, PyReadwriteArrayDyn, + datetime::{Timedelta, units}, }; use pyo3::{ + Bound, FromPyObject, Py, PyAny, PyResult, Python, exceptions::PyIndexError, pyfunction, types::{PyDict, PyDictMethods}, - Bound, FromPyObject, Py, PyAny, PyResult, Python, }; use std::ops::Add; diff --git a/src/array.rs b/src/array.rs index fec289830..79018420d 100644 --- a/src/array.rs +++ b/src/array.rs @@ -15,9 +15,8 @@ use ndarray::{ }; use num_traits::AsPrimitive; use pyo3::{ - ffi, + Borrowed, Bound, CastError, Py, PyAny, PyErr, PyResult, PyTypeCheck, PyTypeInfo, Python, ffi, types::{DerefToPyAny, PyModule}, - Borrowed, Bound, CastError, Py, PyAny, PyErr, PyResult, PyTypeCheck, PyTypeInfo, Python, }; use crate::borrow::{PyReadonlyArray, PyReadwriteArray}; @@ -25,10 +24,10 @@ use crate::cold; use crate::convert::{ArrayExt, IntoPyArray, NpyIndex, ToNpyDims, ToPyArray}; use crate::dtype::{Element, PyArrayDescrMethods}; use crate::error::{ - AsSliceError, BorrowError, DimensionalityError, FromVecError, IgnoreError, TypeError, - DIMENSIONALITY_MISMATCH_ERR, MAX_DIMENSIONALITY_ERR, + AsSliceError, BorrowError, DIMENSIONALITY_MISMATCH_ERR, DimensionalityError, FromVecError, + IgnoreError, MAX_DIMENSIONALITY_ERR, TypeError, }; -use crate::npyffi::{self, _PyArray_GET_ITEM_DATA, npy_intp, NPY_ORDER, PY_ARRAY_API}; +use crate::npyffi::{self, _PyArray_GET_ITEM_DATA, NPY_ORDER, PY_ARRAY_API, npy_intp}; use crate::slice_container::PySliceContainer; use crate::untyped_array::{PyUntypedArray, PyUntypedArrayMethods}; @@ -222,7 +221,7 @@ impl PyArray { ID: IntoDimension, { let flags = c_int::from(is_fortran); - Self::new_uninit(py, dims, ptr::null_mut(), flags) + unsafe { Self::new_uninit(py, dims, ptr::null_mut(), flags) } } pub(crate) unsafe fn new_uninit<'py, ID>( @@ -235,19 +234,21 @@ impl PyArray { ID: IntoDimension, { let mut dims = dims.into_dimension(); - let ptr = PY_ARRAY_API.PyArray_NewFromDescr( - py, - npyffi::get_type_object(py, npyffi::NpyTypes::PyArray_Type), - T::get_dtype(py).into_dtype_ptr(), - dims.ndim_cint(), - dims.as_dims_ptr(), - strides as *mut npy_intp, // strides - ptr::null_mut(), // data - flag, // flag - ptr::null_mut(), // obj - ); + let ptr = unsafe { + PY_ARRAY_API.PyArray_NewFromDescr( + py, + npyffi::get_type_object(py, npyffi::NpyTypes::PyArray_Type), + T::get_dtype(py).into_dtype_ptr(), + dims.ndim_cint(), + dims.as_dims_ptr(), + strides as *mut npy_intp, // strides + ptr::null_mut(), // data + flag, // flag + ptr::null_mut(), // obj + ) + }; - Bound::from_owned_ptr(py, ptr).cast_into_unchecked() + unsafe { Bound::from_owned_ptr(py, ptr).cast_into_unchecked() } } unsafe fn new_with_data<'py, ID>( @@ -261,25 +262,29 @@ impl PyArray { ID: IntoDimension, { let mut dims = dims.into_dimension(); - let ptr = PY_ARRAY_API.PyArray_NewFromDescr( - py, - npyffi::get_type_object(py, npyffi::NpyTypes::PyArray_Type), - T::get_dtype(py).into_dtype_ptr(), - dims.ndim_cint(), - dims.as_dims_ptr(), - strides as *mut npy_intp, // strides - data_ptr as *mut c_void, // data - npyffi::NPY_ARRAY_WRITEABLE, // flag - ptr::null_mut(), // obj - ); - - PY_ARRAY_API.PyArray_SetBaseObject( - py, - ptr as *mut npyffi::PyArrayObject, - container as *mut ffi::PyObject, - ); - - Bound::from_owned_ptr(py, ptr).cast_into_unchecked() + let ptr = unsafe { + PY_ARRAY_API.PyArray_NewFromDescr( + py, + npyffi::get_type_object(py, npyffi::NpyTypes::PyArray_Type), + T::get_dtype(py).into_dtype_ptr(), + dims.ndim_cint(), + dims.as_dims_ptr(), + strides as *mut npy_intp, // strides + data_ptr as *mut c_void, // data + npyffi::NPY_ARRAY_WRITEABLE, // flag + ptr::null_mut(), // obj + ) + }; + + unsafe { + PY_ARRAY_API.PyArray_SetBaseObject( + py, + ptr as *mut npyffi::PyArrayObject, + container as *mut ffi::PyObject, + ) + }; + + unsafe { Bound::from_owned_ptr(py, ptr).cast_into_unchecked() } } pub(crate) unsafe fn from_raw_parts<'py>( @@ -293,7 +298,7 @@ impl PyArray { .expect("Failed to create slice container") .into_ptr(); - Self::new_with_data(py, dims, strides, data_ptr, container.cast()) + unsafe { Self::new_with_data(py, dims, strides, data_ptr, container.cast()) } } /// Creates a NumPy array backed by `array` and ties its ownership to the Python object `container`. @@ -341,13 +346,15 @@ impl PyArray { let py = container.py(); - Self::new_with_data( - py, - dims, - strides.as_ptr(), - data_ptr, - container.into_ptr().cast(), - ) + unsafe { + Self::new_with_data( + py, + dims, + strides.as_ptr(), + data_ptr, + container.into_ptr().cast(), + ) + } } /// Construct a new NumPy array filled with zeros. @@ -707,12 +714,16 @@ impl> PyArray { unsafe fn clone_elements(py: Python<'_>, elems: &[T], data_ptr: &mut *mut T) { if T::IS_COPY { - ptr::copy_nonoverlapping(elems.as_ptr(), *data_ptr, elems.len()); - *data_ptr = data_ptr.add(elems.len()); + unsafe { + ptr::copy_nonoverlapping(elems.as_ptr(), *data_ptr, elems.len()); + *data_ptr = data_ptr.add(elems.len()); + } } else { for elem in elems { - data_ptr.write(elem.clone_ref(py)); - *data_ptr = data_ptr.add(1); + unsafe { + data_ptr.write(elem.clone_ref(py)); + *data_ptr = data_ptr.add(1); + } } } } @@ -755,7 +766,10 @@ pub trait PyArrayMethods<'py, T, D>: PyUntypedArrayMethods<'py> + Sized { // the underlying pointer is aligned or not. Ok(&[]) } else if self.is_aligned() && self.is_contiguous() { - Ok(slice::from_raw_parts(self.data(), len)) + // SAFETY: + // - the pointer is non-null, aligned and contiguous for `len` elements + // - no mutable aliasing is promised by the caller + unsafe { Ok(slice::from_raw_parts(self.data(), len)) } } else { Err(AsSliceError) } @@ -782,7 +796,10 @@ pub trait PyArrayMethods<'py, T, D>: PyUntypedArrayMethods<'py> + Sized { // the underlying pointer is aligned or not. Ok(&mut []) } else if self.is_aligned() && self.is_contiguous() { - Ok(slice::from_raw_parts_mut(self.data(), len)) + // SAFETY: + // - the pointer is non-null, aligned and contiguous for `len` elements + // - no aliasing (mutable or not) is promised by the caller + unsafe { Ok(slice::from_raw_parts_mut(self.data(), len)) } } else { Err(AsSliceError) } @@ -879,7 +896,7 @@ pub trait PyArrayMethods<'py, T, D>: PyUntypedArrayMethods<'py> + Sized { D: Dimension, Idx: NpyIndex, { - &*self.uget_raw(index) + unsafe { &*self.uget_raw(index) } } /// Same as [`uget`](Self::uget), but returns `&mut T`. @@ -899,7 +916,7 @@ pub trait PyArrayMethods<'py, T, D>: PyUntypedArrayMethods<'py> + Sized { D: Dimension, Idx: NpyIndex, { - &mut *self.uget_raw(index) + unsafe { &mut *self.uget_raw(index) } } /// Same as [`uget`][Self::uget], but returns `*mut T`. @@ -915,7 +932,8 @@ pub trait PyArrayMethods<'py, T, D>: PyUntypedArrayMethods<'py> + Sized { Idx: NpyIndex, { let offset = index.get_unchecked::(self.strides()); - self.data().offset(offset) as *mut _ + // SAFETY: valid index is promised by the caller + unsafe { self.data().offset(offset).cast() } } /// Get a copy of the specified element in the array. @@ -1491,7 +1509,7 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray> { D: Dimension, { let ptr = get_raw(self, index)?; - Some(&*ptr) + unsafe { Some(&*ptr) } } #[inline(always)] @@ -1501,7 +1519,7 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray> { D: Dimension, { let ptr = get_raw(self, index)?; - Some(&mut *ptr) + unsafe { Some(&mut *ptr) } } fn get_owned(&self, index: Idx) -> Option @@ -1564,7 +1582,9 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray> { T: Element, D: Dimension, { - as_view(self, |shape, ptr| ArrayView::from_shape_ptr(shape, ptr)) + as_view(self, |shape, ptr| unsafe { + ArrayView::from_shape_ptr(shape, ptr) + }) } unsafe fn as_array_mut(&self) -> ArrayViewMut<'_, T, D> @@ -1572,7 +1592,9 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray> { T: Element, D: Dimension, { - as_view(self, |shape, ptr| ArrayViewMut::from_shape_ptr(shape, ptr)) + as_view(self, |shape, ptr| unsafe { + ArrayViewMut::from_shape_ptr(shape, ptr) + }) } fn as_raw_array(&self) -> RawArrayView @@ -1676,13 +1698,15 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray> { let mut newshape = newshape.to_npy_dims(); let py = self.py(); - let res = PY_ARRAY_API.PyArray_Resize( - py, - self.as_array_ptr(), - &mut newshape as *mut npyffi::PyArray_Dims, - 1, - NPY_ORDER::NPY_ANYORDER, - ); + let res = unsafe { + PY_ARRAY_API.PyArray_Resize( + py, + self.as_array_ptr(), + &mut newshape as *mut npyffi::PyArray_Dims, + 1, + NPY_ORDER::NPY_ANYORDER, + ) + }; if !res.is_null() { Ok(()) @@ -1705,7 +1729,7 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray> { { let (shape, strides) = try_as_matrix_shape_strides(self)?; - let storage = nalgebra::ViewStorage::from_raw_parts(self.data(), shape, strides); + let storage = unsafe { nalgebra::ViewStorage::from_raw_parts(self.data(), shape, strides) }; Some(nalgebra::Matrix::from_data(storage)) } @@ -1724,7 +1748,8 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray> { { let (shape, strides) = try_as_matrix_shape_strides(self)?; - let storage = nalgebra::ViewStorageMut::from_raw_parts(self.data(), shape, strides); + let storage = + unsafe { nalgebra::ViewStorageMut::from_raw_parts(self.data(), shape, strides) }; Some(nalgebra::Matrix::from_data(storage)) } diff --git a/src/array_like.rs b/src/array_like.rs index 47032f087..8190bbb94 100644 --- a/src/array_like.rs +++ b/src/array_like.rs @@ -2,11 +2,11 @@ use std::marker::PhantomData; use std::ops::Deref; use ndarray::{Array1, Dimension, Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn}; -use pyo3::{types::PyAnyMethods, Borrowed, FromPyObject, PyAny, PyErr, PyResult}; +use pyo3::{Borrowed, FromPyObject, PyAny, PyErr, PyResult, types::PyAnyMethods}; use crate::npyffi::NPY_ARRAY_FORCECAST; -use crate::{array::PyArrayMethods, PY_ARRAY_API}; use crate::{Element, IntoPyArray, PyArray, PyReadonlyArray, PyUntypedArray}; +use crate::{PY_ARRAY_API, array::PyArrayMethods}; pub trait Coerce: Sealed { const ALLOW_TYPE_CHANGE: bool; diff --git a/src/borrow/mod.rs b/src/borrow/mod.rs index 4f8136e47..2d42fa05e 100644 --- a/src/borrow/mod.rs +++ b/src/borrow/mod.rs @@ -677,8 +677,8 @@ mod tests { use super::*; use pyo3::{ - types::{IntoPyDict, PyAnyMethods}, Python, + types::{IntoPyDict, PyAnyMethods}, }; use crate::array::PyArray1; diff --git a/src/borrow/shared.rs b/src/borrow/shared.rs index 9ab5a8ce0..0dac17883 100644 --- a/src/borrow/shared.rs +++ b/src/borrow/shared.rs @@ -8,14 +8,14 @@ use std::sync::Mutex; use num_integer::gcd; use pyo3::sync::PyOnceLock; use pyo3::types::{PyAnyMethods, PyCapsuleMethods}; -use pyo3::{exceptions::PyTypeError, types::PyCapsule, PyResult, Python}; +use pyo3::{PyResult, Python, exceptions::PyTypeError, types::PyCapsule}; use rustc_hash::FxHashMap; use crate::array::get_array_module; use crate::cold; use crate::error::BorrowError; use crate::npyffi::{ - _PyArray_GET_ITEM_DATA, PyArrayObject, PyArray_Check, PyDataType_ELSIZE, NPY_ARRAY_WRITEABLE, + _PyArray_GET_ITEM_DATA, NPY_ARRAY_WRITEABLE, PyArray_Check, PyArrayObject, PyDataType_ELSIZE, }; /// Defines the shared C API used for borrow checking @@ -45,8 +45,8 @@ unsafe impl Send for Shared {} unsafe extern "C" fn acquire_shared(flags: *mut c_void, array: *mut PyArrayObject) -> c_int { // SAFETY: must be attached when calling `acquire_shared`. - let py = Python::assume_attached(); - let flags = &*(flags as *mut BorrowFlags); + let py = unsafe { Python::assume_attached() }; + let flags = unsafe { &*(flags as *mut BorrowFlags) }; let address = base_address(py, array); let key = borrow_key(py, array); @@ -58,13 +58,13 @@ unsafe extern "C" fn acquire_shared(flags: *mut c_void, array: *mut PyArrayObjec } unsafe extern "C" fn acquire_mut_shared(flags: *mut c_void, array: *mut PyArrayObject) -> c_int { - if (*_PyArray_GET_ITEM_DATA(array)).flags & NPY_ARRAY_WRITEABLE == 0 { + if unsafe { (*_PyArray_GET_ITEM_DATA(array)).flags } & NPY_ARRAY_WRITEABLE == 0 { return -2; } - // SAFETY: must be attached when calling `acquire_shared`. - let py = Python::assume_attached(); - let flags = &*(flags as *mut BorrowFlags); + // SAFETY: must be attached when calling `acquire_mut_shared`. + let py = unsafe { Python::assume_attached() }; + let flags = unsafe { &*(flags as *mut BorrowFlags) }; let address = base_address(py, array); let key = borrow_key(py, array); @@ -76,9 +76,9 @@ unsafe extern "C" fn acquire_mut_shared(flags: *mut c_void, array: *mut PyArrayO } unsafe extern "C" fn release_shared(flags: *mut c_void, array: *mut PyArrayObject) { - // SAFETY: must be attached when calling `acquire_shared`. - let py = Python::assume_attached(); - let flags = &*(flags as *mut BorrowFlags); + // SAFETY: must be attached when calling `release_shared`. + let py = unsafe { Python::assume_attached() }; + let flags = unsafe { &*(flags as *mut BorrowFlags) }; let address = base_address(py, array); let key = borrow_key(py, array); @@ -86,9 +86,9 @@ unsafe extern "C" fn release_shared(flags: *mut c_void, array: *mut PyArrayObjec } unsafe extern "C" fn release_mut_shared(flags: *mut c_void, array: *mut PyArrayObject) { - // SAFETY: must be attached when calling `acquire_shared`. - let py = Python::assume_attached(); - let flags = &*(flags as *mut BorrowFlags); + // SAFETY: must be attached when calling `release_mut_shared`. + let py = unsafe { Python::assume_attached() }; + let flags = unsafe { &*(flags as *mut BorrowFlags) }; let address = base_address(py, array); let key = borrow_key(py, array); diff --git a/src/datetime.rs b/src/datetime.rs index e484a5671..91b28f285 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -64,9 +64,9 @@ use pyo3::sync::MutexExt; use pyo3::{Bound, Py, Python}; use rustc_hash::FxHashMap; -use crate::dtype::{clone_methods_impl, Element, PyArrayDescr, PyArrayDescrMethods}; +use crate::dtype::{Element, PyArrayDescr, PyArrayDescrMethods, clone_methods_impl}; use crate::npyffi::{ - PyArray_DatetimeDTypeMetaData, PyDataType_C_METADATA, NPY_DATETIMEUNIT, NPY_TYPES, + NPY_DATETIMEUNIT, NPY_TYPES, PyArray_DatetimeDTypeMetaData, PyDataType_C_METADATA, }; /// Represents the [datetime units][datetime-units] supported by NumPy diff --git a/src/dtype.rs b/src/dtype.rs index c5ec13d80..c1b9af0af 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -8,18 +8,18 @@ use num_traits::{Bounded, Zero}; #[cfg(feature = "half")] use pyo3::sync::PyOnceLock; use pyo3::{ + Borrowed, Bound, Py, PyAny, PyResult, PyTypeInfo, Python, conversion::IntoPyObject, exceptions::{PyIndexError, PyValueError}, ffi::{self, PyTuple_Size}, pyobject_native_type_named, types::{PyAnyMethods, PyDict, PyDictMethods, PyTuple, PyType}, - Borrowed, Bound, Py, PyAny, PyResult, PyTypeInfo, Python, }; use crate::npyffi::{ - self, _PyDataType_GET_ITEM_DATA, NpyTypes, PyArray_Descr, PyDataType_ALIGNMENT, - PyDataType_ELSIZE, PyDataType_FIELDS, PyDataType_FLAGS, PyDataType_NAMES, PyDataType_SUBARRAY, - NPY_ALIGNED_STRUCT, NPY_BYTEORDER_CHAR, NPY_ITEM_HASOBJECT, NPY_TYPES, PY_ARRAY_API, + self, _PyDataType_GET_ITEM_DATA, NPY_ALIGNED_STRUCT, NPY_BYTEORDER_CHAR, NPY_ITEM_HASOBJECT, + NPY_TYPES, NpyTypes, PY_ARRAY_API, PyArray_Descr, PyDataType_ALIGNMENT, PyDataType_ELSIZE, + PyDataType_FIELDS, PyDataType_FLAGS, PyDataType_NAMES, PyDataType_SUBARRAY, }; pub use num_complex::{Complex32, Complex64}; @@ -659,14 +659,16 @@ mod tests { use pyo3::types::PyString; use pyo3::{py_run, types::PyTypeMethods}; - use crate::npyffi::{is_numpy_2, NPY_NEEDS_PYAPI}; + use crate::npyffi::{NPY_NEEDS_PYAPI, is_numpy_2}; #[test] fn test_dtype_new() { Python::attach(|py| { - assert!(PyArrayDescr::new(py, "float64") - .unwrap() - .is(dtype::(py))); + assert!( + PyArrayDescr::new(py, "float64") + .unwrap() + .is(dtype::(py)) + ); let dt = PyArrayDescr::new(py, [("a", "O"), ("b", "?")].as_ref()).unwrap(); assert_eq!(dt.names(), Some(vec!["a".to_owned(), "b".to_owned()])); diff --git a/src/error.rs b/src/error.rs index 6041b420a..88e94a892 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,8 +4,8 @@ use std::error::Error; use std::fmt; use pyo3::{ - conversion::IntoPyObject, exceptions::PyTypeError, Bound, Py, PyAny, PyErr, PyErrArguments, - Python, + Bound, Py, PyAny, PyErr, PyErrArguments, Python, conversion::IntoPyObject, + exceptions::PyTypeError, }; use crate::dtype::PyArrayDescr; diff --git a/src/lib.rs b/src/lib.rs index 23a96083c..2a670d6f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,8 +92,8 @@ pub use pyo3; pub use nalgebra; pub use crate::array::{ - get_array_module, PyArray, PyArray0, PyArray0Methods, PyArray1, PyArray2, PyArray3, PyArray4, - PyArray5, PyArray6, PyArrayDyn, PyArrayMethods, + PyArray, PyArray0, PyArray0Methods, PyArray1, PyArray2, PyArray3, PyArray4, PyArray5, PyArray6, + PyArrayDyn, PyArrayMethods, get_array_module, }; pub use crate::array_like::{ AllowTypeChange, PyArrayLike, PyArrayLike0, PyArrayLike1, PyArrayLike2, PyArrayLike3, @@ -106,14 +106,14 @@ pub use crate::borrow::{ PyReadwriteArray5, PyReadwriteArray6, PyReadwriteArrayDyn, }; pub use crate::convert::{IntoPyArray, NpyIndex, ToNpyDims, ToPyArray}; -pub use crate::dtype::{dtype, Complex32, Complex64, Element, PyArrayDescr, PyArrayDescrMethods}; +pub use crate::dtype::{Complex32, Complex64, Element, PyArrayDescr, PyArrayDescrMethods, dtype}; pub use crate::error::{AsSliceError, BorrowError, FromVecError}; pub use crate::npyffi::{PY_ARRAY_API, PY_UFUNC_API}; pub use crate::strings::{PyFixedString, PyFixedUnicode}; pub use crate::sum_products::{dot, einsum, inner}; pub use crate::untyped_array::{PyUntypedArray, PyUntypedArrayMethods}; -pub use ndarray::{array, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn}; +pub use ndarray::{Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn, array}; /// A prelude /// diff --git a/src/npyffi/array.rs b/src/npyffi/array.rs index 4c4e15019..246c76149 100644 --- a/src/npyffi/array.rs +++ b/src/npyffi/array.rs @@ -83,13 +83,15 @@ unsafe impl Send for PyArrayAPI {} unsafe impl Sync for PyArrayAPI {} impl PyArrayAPI { + /// # Safety + /// `offset` has to in bounds of the array api capsule allocation pub(super) unsafe fn get<'py>(&self, py: Python<'py>, offset: isize) -> NonNull<*const c_void> { let api = self .0 .get_or_try_init(py, || -> PyResult<_> { let api = get_numpy_api(py, mod_name(py)?, CAPSULE_NAME)?; - let module_version = { + let module_version = unsafe { // unsigned int PyArray_GetNDArrayCVersion(); let get_abi_version: extern "C" fn() -> c_uint = api.add(0).cast().read(); @@ -140,7 +142,8 @@ impl PyArrayAPI { }) .expect("Failed to access NumPy array API capsule"); - api.offset(offset) + // SAFETY: guaranteed by caller + unsafe { api.offset(offset) } } } @@ -450,13 +453,13 @@ impl PyArrayAPI { /// Checks that `op` is an instance of `PyArray` or not. #[allow(non_snake_case)] pub unsafe fn PyArray_Check<'py>(py: Python<'py>, op: *mut PyObject) -> c_int { - ffi::PyObject_TypeCheck(op, super::get_type_object(py, NpyTypes::PyArray_Type)) + unsafe { ffi::PyObject_TypeCheck(op, super::get_type_object(py, NpyTypes::PyArray_Type)) } } /// Checks that `op` is an exact instance of `PyArray` or not. #[allow(non_snake_case)] pub unsafe fn PyArray_CheckExact<'py>(py: Python<'py>, op: *mut PyObject) -> c_int { - (ffi::Py_TYPE(op) == super::get_type_object(py, NpyTypes::PyArray_Type)) as _ + unsafe { (ffi::Py_TYPE(op) == super::get_type_object(py, NpyTypes::PyArray_Type)) as _ } } #[cfg(test)] diff --git a/src/npyffi/mod.rs b/src/npyffi/mod.rs index 90728747a..f27c588c3 100644 --- a/src/npyffi/mod.rs +++ b/src/npyffi/mod.rs @@ -21,10 +21,10 @@ use std::mem::forget; use std::ptr::NonNull; use pyo3::{ + PyResult, Python, ffi::PyTypeObject, sync::PyOnceLock, types::{PyAnyMethods, PyCapsule, PyCapsuleMethods, PyModule}, - PyResult, Python, }; static API_VERSION: PyOnceLock = PyOnceLock::new(); @@ -63,7 +63,7 @@ macro_rules! impl_api { [$offset: expr; $vis:vis $fname: ident ($($arg: ident: $t: ty),* $(,)?) $(-> $ret: ty)?] => { #[allow(non_snake_case)] $vis unsafe fn $fname<'py>(&self, py: Python<'py>, $($arg : $t), *) $(-> $ret)* { - let f: extern "C" fn ($($arg : $t), *) $(-> $ret)* = self.get(py, $offset).cast().read(); + let f: extern "C" fn ($($arg : $t), *) $(-> $ret)* = unsafe { self.get(py, $offset).cast().read() }; f($($arg), *) } } @@ -79,7 +79,7 @@ macro_rules! impl_array_type { /// Get a pointer of the type object associated with `ty`. pub unsafe fn get_type_object<'py>(py: Python<'py>, ty: NpyTypes) -> *mut PyTypeObject { match ty { - $( NpyTypes::$tname => $api.get(py, $offset).read() as _ ),* + $( NpyTypes::$tname => unsafe { $api.get(py, $offset).read() as _ }),* } } } diff --git a/src/npyffi/objects.rs b/src/npyffi/objects.rs index e87c8a05f..ca4ebd297 100644 --- a/src/npyffi/objects.rs +++ b/src/npyffi/objects.rs @@ -141,7 +141,7 @@ pub unsafe fn _PyArray_GET_ITEM_DATA(arr: *const PyArrayObject) -> *mut PyArrayO PY_ARRAY_API._PyArray_GET_ITEM_DATA(Python::assume_attached(), arr) } #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))] - { + unsafe { &raw mut (*arr.cast_mut()).fields } } @@ -156,7 +156,7 @@ pub unsafe fn _PyDataType_GET_ITEM_DATA(dtype: *const PyArray_Descr) -> *mut PyA PY_ARRAY_API._PyDataType_GET_ITEM_DATA(Python::assume_attached(), dtype) } #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))] - { + unsafe { &raw mut (*dtype.cast_mut()).fields } } @@ -173,7 +173,7 @@ unsafe fn _PyArray_LegacyDescr_GET_ITEM_DATA( PY_ARRAY_API._PyArray_LegacyDescr_GET_ITEM_DATA(Python::assume_attached(), dtype) } #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))] - { + unsafe { &raw mut (*dtype.cast_mut()).fields } } @@ -181,8 +181,10 @@ unsafe fn _PyArray_LegacyDescr_GET_ITEM_DATA( #[allow(non_snake_case)] #[inline(always)] pub unsafe fn PyDataType_ISLEGACY(dtype: *const PyArray_Descr) -> bool { - let dtype = _PyDataType_GET_ITEM_DATA(dtype); - (*dtype).type_num < NPY_TYPES::NPY_VSTRING as _ && (*dtype).type_num >= 0 + unsafe { + let dtype = _PyDataType_GET_ITEM_DATA(dtype); + (*dtype).type_num < NPY_TYPES::NPY_VSTRING as _ && (*dtype).type_num >= 0 + } } #[allow(non_snake_case)] @@ -232,7 +234,7 @@ macro_rules! define_descr_accessor { #[allow(non_snake_case)] #[inline(always)] pub unsafe fn $name<'py>(py: Python<'py>, dtype: *const PyArray_Descr) -> $type { - if $legacy_only && !PyDataType_ISLEGACY(dtype) { + if $legacy_only && unsafe { !PyDataType_ISLEGACY(dtype) } { $default } else { #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))] diff --git a/src/npyffi/ufunc.rs b/src/npyffi/ufunc.rs index c170a9115..b6b2dfdb8 100644 --- a/src/npyffi/ufunc.rs +++ b/src/npyffi/ufunc.rs @@ -29,13 +29,16 @@ unsafe impl Send for PyUFuncAPI {} unsafe impl Sync for PyUFuncAPI {} impl PyUFuncAPI { + /// # Safety + /// `offset` has to in bounds of the ufunc api capsule allocation pub(super) unsafe fn get<'py>(&self, py: Python<'py>, offset: isize) -> NonNull<*const c_void> { let api = self .0 .get_or_try_init(py, || get_numpy_api(py, mod_name(py)?, CAPSULE_NAME)) .expect("Failed to access NumPy ufunc API capsule"); - api.offset(offset) + // SAFETY: guaranteed by caller + unsafe { api.offset(offset) } } } diff --git a/src/random.rs b/src/random.rs index df314236c..acadee98a 100644 --- a/src/random.rs +++ b/src/random.rs @@ -97,12 +97,12 @@ use std::ptr::NonNull; use pyo3::{ + PyTypeInfo, exceptions::PyRuntimeError, ffi, intern, prelude::*, sync::PyOnceLock, types::{DerefToPyAny, PyCapsule, PyType}, - PyTypeInfo, }; use crate::npyffi::bitgen_t; diff --git a/src/slice_container.rs b/src/slice_container.rs index 0c29eae61..991dbe931 100644 --- a/src/slice_container.rs +++ b/src/slice_container.rs @@ -25,8 +25,11 @@ unsafe impl Sync for PySliceContainer {} impl From> for PySliceContainer { fn from(data: Box<[T]>) -> Self { + /// # Safety + /// `ptr` and `len` need to originate from a boxed slice unsafe fn drop_boxed_slice(ptr: *mut u8, len: usize, _cap: usize) { - let _ = Box::from_raw(ptr::slice_from_raw_parts_mut(ptr as *mut T, len)); + // SAFETY: guaranteed by caller + let _ = unsafe { Box::from_raw(ptr::slice_from_raw_parts_mut(ptr as *mut T, len)) }; } // FIXME(adamreichold): Use `Box::into_raw` when @@ -49,8 +52,11 @@ impl From> for PySliceContainer { impl From> for PySliceContainer { fn from(data: Vec) -> Self { + /// # Safety + /// See [Vec::from_raw_parts] unsafe fn drop_vec(ptr: *mut u8, len: usize, cap: usize) { - let _ = Vec::from_raw_parts(ptr as *mut T, len, cap); + // Safety: guaranteed by caller + let _ = unsafe { Vec::from_raw_parts(ptr as *mut T, len, cap) }; } // FIXME(adamreichold): Use `Vec::into_raw_parts` diff --git a/src/strings.rs b/src/strings.rs index 0e429f4c7..d996c4c03 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -12,13 +12,13 @@ use std::sync::Mutex; use pyo3::sync::MutexExt; use pyo3::{ - ffi::{Py_UCS1, Py_UCS4}, Bound, Py, Python, + ffi::{Py_UCS1, Py_UCS4}, }; use rustc_hash::FxHashMap; -use crate::dtype::{clone_methods_impl, Element, PyArrayDescr, PyArrayDescrMethods}; -use crate::npyffi::{_PyDataType_GET_ITEM_DATA, PyDataType_SET_ELSIZE, NPY_TYPES}; +use crate::dtype::{Element, PyArrayDescr, PyArrayDescrMethods, clone_methods_impl}; +use crate::npyffi::{_PyDataType_GET_ITEM_DATA, NPY_TYPES, PyDataType_SET_ELSIZE}; /// A newtype wrapper around [`[u8; N]`][Py_UCS1] to handle [`byte` scalars][numpy-bytes] while satisfying coherence. /// @@ -188,9 +188,11 @@ impl TypeDescriptors { Entry::Vacant(entry) => { let dtype = PyArrayDescr::new_from_npy_type(py, npy_type); - let descr = &mut *dtype.as_dtype_ptr(); - PyDataType_SET_ELSIZE(py, descr, size.try_into().unwrap()); - (*_PyDataType_GET_ITEM_DATA(descr)).byteorder = byteorder; + let descr = dtype.as_dtype_ptr(); + unsafe { + PyDataType_SET_ELSIZE(py, descr, size.try_into().unwrap()); + (*_PyDataType_GET_ITEM_DATA(descr)).byteorder = byteorder; + } entry.insert(dtype.into()) } @@ -225,8 +227,10 @@ mod tests { "🦀🐍" ); assert_eq!( - PyFixedUnicode([b'f' as _, b'o' as _, b'o' as _, b'b' as _, b'a' as _, b'r' as _]) - .to_string(), + PyFixedUnicode([ + b'f' as _, b'o' as _, b'o' as _, b'b' as _, b'a' as _, b'r' as _ + ]) + .to_string(), "foobar" ); } diff --git a/src/sum_products.rs b/src/sum_products.rs index 0c50c2d8f..f6bb235d9 100644 --- a/src/sum_products.rs +++ b/src/sum_products.rs @@ -8,7 +8,7 @@ use pyo3::{Borrowed, Bound, FromPyObject, PyResult}; use crate::array::PyArray; use crate::dtype::Element; -use crate::npyffi::{array::PY_ARRAY_API, NPY_CASTING, NPY_ORDER}; +use crate::npyffi::{NPY_CASTING, NPY_ORDER, array::PY_ARRAY_API}; /// Return value of a function that can yield either an array or a scalar. pub trait ArrayOrScalar<'a, 'py, T>: FromPyObject<'a, 'py> {} diff --git a/src/untyped_array.rs b/src/untyped_array.rs index f7716ab33..d791e5784 100644 --- a/src/untyped_array.rs +++ b/src/untyped_array.rs @@ -3,7 +3,7 @@ //! [ndarray]: https://numpy.org/doc/stable/reference/arrays.ndarray.html use std::slice; -use pyo3::{ffi, pyobject_native_type_named, Bound, PyAny, PyTypeInfo, Python}; +use pyo3::{Bound, PyAny, PyTypeInfo, Python, ffi, pyobject_native_type_named}; use crate::array::{PyArray, PyArrayMethods}; use crate::cold; diff --git a/tests/array.rs b/tests/array.rs index 2441be0f9..a960c5d52 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -2,16 +2,15 @@ use std::mem::size_of; #[cfg(feature = "half")] use half::{bf16, f16}; -use ndarray::{array, s, Array1, Dim}; +use ndarray::{Array1, Dim, array, s}; use numpy::prelude::*; use numpy::{ - dtype, get_array_module, npyffi::NPY_ORDER, pyarray, PyArray, PyArray1, PyArray2, PyArrayDescr, - PyFixedString, PyFixedUnicode, + PyArray, PyArray1, PyArray2, PyArrayDescr, PyFixedString, PyFixedUnicode, dtype, + get_array_module, npyffi::NPY_ORDER, pyarray, }; use pyo3::{ - py_run, pyclass, pymethods, + Bound, Py, Python, py_run, pyclass, pymethods, types::{IntoPyDict, PyAnyMethods, PyDict, PyList}, - Bound, Py, Python, }; fn get_np_locals(py: Python<'_>) -> Bound<'_, PyDict> { diff --git a/tests/array_like.rs b/tests/array_like.rs index b0fc06112..c118119ac 100644 --- a/tests/array_like.rs +++ b/tests/array_like.rs @@ -1,11 +1,11 @@ use ndarray::array; use numpy::{ - get_array_module, AllowTypeChange, PyArrayLike1, PyArrayLike2, PyArrayLikeDyn, - PyUntypedArrayMethods as _, + AllowTypeChange, PyArrayLike1, PyArrayLike2, PyArrayLikeDyn, PyUntypedArrayMethods as _, + get_array_module, }; use pyo3::{ - types::{IntoPyDict, PyAnyMethods, PyDict}, Bound, Python, + types::{IntoPyDict, PyAnyMethods, PyDict}, }; fn get_np_locals(py: Python<'_>) -> Bound<'_, PyDict> { diff --git a/tests/borrow.rs b/tests/borrow.rs index 5e121eb81..66761337f 100644 --- a/tests/borrow.rs +++ b/tests/borrow.rs @@ -1,14 +1,13 @@ use std::thread::spawn; use numpy::{ + PyArray, PyArray1, PyArray2, PyReadonlyArray3, PyReadwriteArray3, PyUntypedArrayMethods, array::PyArrayMethods, npyffi::{_PyArray_GET_ITEM_DATA, NPY_ARRAY_WRITEABLE}, - PyArray, PyArray1, PyArray2, PyReadonlyArray3, PyReadwriteArray3, PyUntypedArrayMethods, }; use pyo3::{ - pyclass, pymethods, + Py, Python, pyclass, pymethods, types::{IntoPyDict, PyAnyMethods}, - Py, Python, }; #[test] @@ -360,11 +359,12 @@ fn can_make_python_array_nonwriteable() { let array = PyArray1::::zeros(py, 10, false); let locals = [("array", &array)].into_py_dict(py).unwrap(); array.readwrite().make_nonwriteable(); - assert!(!py - .eval(c"array.flags.writeable", None, Some(&locals)) - .unwrap() - .extract::() - .unwrap()) + assert!( + !py.eval(c"array.flags.writeable", None, Some(&locals)) + .unwrap() + .extract::() + .unwrap() + ) }) } diff --git a/tests/sum_products.rs b/tests/sum_products.rs index 93c79717a..0491d16fd 100644 --- a/tests/sum_products.rs +++ b/tests/sum_products.rs @@ -1,5 +1,5 @@ use numpy::prelude::*; -use numpy::{array, dot, einsum, inner, pyarray, PyArray0, PyArray1, PyArray2}; +use numpy::{PyArray0, PyArray1, PyArray2, array, dot, einsum, inner, pyarray}; use pyo3::{Bound, Python}; #[test] diff --git a/tests/to_py.rs b/tests/to_py.rs index 89fdf1195..04afd756c 100644 --- a/tests/to_py.rs +++ b/tests/to_py.rs @@ -1,12 +1,11 @@ use std::cmp::Ordering; use std::mem::size_of; -use ndarray::{array, s, Array2, Array3}; -use numpy::{prelude::*, PyArray}; +use ndarray::{Array2, Array3, array, s}; +use numpy::{PyArray, prelude::*}; use pyo3::{ - py_run, + Python, py_run, types::{PyAnyMethods, PyDict, PyString}, - Python, }; #[test] diff --git a/x.py b/x.py index 046230b7c..1094da252 100755 --- a/x.py +++ b/x.py @@ -57,7 +57,13 @@ def default(args): def check(args): - run("cargo", "doc", "--all-features", "--no-deps", env={"RUSTDOCFLAGS": "--deny warnings"}) + run( + "cargo", + "doc", + "--all-features", + "--no-deps", + env={"RUSTDOCFLAGS": "--deny warnings"}, + ) run("cargo", "fmt", "--", "--check") run("cargo", "clippy", "--all-features", "--tests", "--", *DENY_WARNINGS)