Skip to content

Commit 9df4023

Browse files
authored
add support for abi3t (#556)
* add support for abi3t
1 parent 91fbc89 commit 9df4023

15 files changed

Lines changed: 304 additions & 71 deletions

File tree

.github/workflows/ci.yml

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ jobs:
2626
test:
2727
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }}
2828
runs-on: ${{ matrix.platform.os }}
29+
continue-on-error: ${{ startsWith(matrix.python-version, '3.15') }} # remove once 3.15 releases
2930
needs: [lint, check-msrv, examples]
3031
strategy:
3132
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
@@ -40,6 +41,8 @@ jobs:
4041
"3.13",
4142
"3.14",
4243
"3.14t",
44+
"3.15",
45+
"3.15t",
4346
"pypy-3.11",
4447
]
4548
platform:
@@ -78,31 +81,43 @@ jobs:
7881
# no 32 bit windows PyPy
7982
- python-version: pypy-3.11
8083
platform: { os: "windows-latest", python-architecture: "x86", rust-target: "i686-pc-windows-msvc" }
84+
# free-threaded on 32bit only supported on 3.15+
85+
- python-version: 3.14t
86+
platform: { os: "windows-latest", python-architecture: "x86", rust-target: "i686-pc-windows-msvc" }
87+
8188
steps:
8289
- uses: actions/checkout@v6
8390
- name: Set up Python ${{ matrix.python-version }}
8491
uses: actions/setup-python@v6
8592
with:
8693
python-version: ${{ matrix.python-version }}
8794
architecture: ${{ matrix.platform.python-architecture }}
95+
allow-prereleases: ${{ startsWith(matrix.python-version, '3.15') }} # remove once 3.15 releases
96+
- name: Set up UV
97+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
8898
- name: Install Rust
8999
uses: dtolnay/rust-toolchain@stable
90100
with:
91101
targets: ${{ matrix.platform.rust-target }}
92102
- name: Test
93-
run: |
94-
pip install "numpy" ml_dtypes
95-
cargo test --all-features
96103
# Not on PyPy, because no embedding API
97104
if: ${{ !startsWith(matrix.python-version, 'pypy') }}
105+
run: |
106+
uv pip install "numpy" ml_dtypes
107+
cargo test --all-features
108+
- name: Test abi3t
109+
if: ${{ endsWith(matrix.python-version, 't') }}
110+
run: |
111+
cargo test --all-features -F pyo3/abi3t
98112
- name: Test example
99113
run: |
100-
pip install nox
101-
nox -f examples/simple/noxfile.py
114+
uvx nox -f examples/simple/noxfile.py
102115
env:
103116
CARGO_TERM_VERBOSE: true
104117
CARGO_BUILD_TARGET: ${{ matrix.platform.rust-target }}
105118
RUST_BACKTRACE: 1
119+
UV_SYSTEM_PYTHON: true
120+
NOX_DEFAULT_VENV_BACKEND: "uv"
106121

107122
test-numpy1:
108123
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} numpy1

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
- Unreleased
44
- `PyReadonlyArray`/`PyReadwriteArray` extraction now returns a `PyErr` which reports the actual dtype/dimensionality mismatch instead of nonsensical `CastError` messages like “'ndarray' is not an instance of 'ndarray'” ([#562](https://github.com/PyO3/rust-numpy/pull/562))
5+
- Add support for free-threaded stable abi (abi3t) from Python 3.15t+ ([#556](https://github.com/PyO3/rust-numpy/pull/556))
6+
- fixed free-threaded builds for 32 bit platforms from Python 3.15+ ([#556](https://github.com/PyO3/rust-numpy/pull/556))
57

68
- v0.29.0
79
- Fix PyArray_DTypeMeta definition when Py_LIMITED_API is disabled ([#532](https://github.com/PyO3/rust-numpy/pull/532))

src/array.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use crate::error::{
2828
AsSliceError, BorrowError, DimensionalityError, FromVecError, IgnoreError, TypeError,
2929
DIMENSIONALITY_MISMATCH_ERR, MAX_DIMENSIONALITY_ERR,
3030
};
31-
use crate::npyffi::{self, npy_intp, NPY_ORDER, PY_ARRAY_API};
31+
use crate::npyffi::{self, _PyArray_GET_ITEM_DATA, npy_intp, NPY_ORDER, PY_ARRAY_API};
3232
use crate::slice_container::PySliceContainer;
3333
use crate::untyped_array::{PyUntypedArray, PyUntypedArrayMethods};
3434

@@ -1481,7 +1481,7 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray<T, D>> {
14811481

14821482
#[inline(always)]
14831483
fn data(&self) -> *mut T {
1484-
unsafe { (*self.as_array_ptr()).data.cast() }
1484+
unsafe { (*_PyArray_GET_ITEM_DATA(self.as_array_ptr())).data.cast() }
14851485
}
14861486

14871487
#[inline(always)]

src/borrow/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ use crate::convert::NpyIndex;
182182
use crate::dtype::Element;
183183
use crate::error::{AsSliceError, BorrowError};
184184
use crate::npyffi;
185-
use crate::npyffi::flags;
185+
use crate::npyffi::{_PyArray_GET_ITEM_DATA, flags};
186186
use crate::untyped_array::PyUntypedArrayMethods;
187187

188188
use shared::{acquire, acquire_mut, release, release_mut};
@@ -538,7 +538,7 @@ where
538538
// SAFETY: consuming the only extant mutable reference guarantees we cannot invalidate an
539539
// existing reference, nor allow the caller to keep hold of one.
540540
unsafe {
541-
(*self.as_array_ptr()).flags &= !flags::NPY_ARRAY_WRITEABLE;
541+
(*_PyArray_GET_ITEM_DATA(self.as_array_ptr())).flags &= !flags::NPY_ARRAY_WRITEABLE;
542542
}
543543
self.into()
544544
}

src/borrow/shared.rs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ use rustc_hash::FxHashMap;
1515
use crate::array::get_array_module;
1616
use crate::cold;
1717
use crate::error::BorrowError;
18-
use crate::npyffi::{PyArrayObject, PyArray_Check, PyDataType_ELSIZE, NPY_ARRAY_WRITEABLE};
18+
use crate::npyffi::{
19+
_PyArray_GET_ITEM_DATA, PyArrayObject, PyArray_Check, PyDataType_ELSIZE, NPY_ARRAY_WRITEABLE,
20+
};
1921

2022
/// Defines the shared C API used for borrow checking
2123
///
@@ -57,7 +59,7 @@ unsafe extern "C" fn acquire_shared(flags: *mut c_void, array: *mut PyArrayObjec
5759
}
5860

5961
unsafe extern "C" fn acquire_mut_shared(flags: *mut c_void, array: *mut PyArrayObject) -> c_int {
60-
if (*array).flags & NPY_ARRAY_WRITEABLE == 0 {
62+
if (*_PyArray_GET_ITEM_DATA(array)).flags & NPY_ARRAY_WRITEABLE == 0 {
6163
return -2;
6264
}
6365

@@ -368,7 +370,7 @@ impl BorrowFlags {
368370

369371
fn base_address<'py>(py: Python<'py>, mut array: *mut PyArrayObject) -> *mut c_void {
370372
loop {
371-
let base = unsafe { (*array).base };
373+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(array)).base };
372374

373375
if base.is_null() {
374376
return array as *mut c_void;
@@ -383,7 +385,7 @@ fn base_address<'py>(py: Python<'py>, mut array: *mut PyArrayObject) -> *mut c_v
383385
fn borrow_key<'py>(py: Python<'py>, array: *mut PyArrayObject) -> BorrowKey {
384386
let range = data_range(py, array);
385387

386-
let data_ptr = unsafe { (*array).data };
388+
let data_ptr = unsafe { (*_PyArray_GET_ITEM_DATA(array)).data };
387389
let gcd_strides = gcd_strides(array);
388390

389391
BorrowKey {
@@ -394,6 +396,7 @@ fn borrow_key<'py>(py: Python<'py>, array: *mut PyArrayObject) -> BorrowKey {
394396
}
395397

396398
fn data_range<'py>(py: Python<'py>, array: *mut PyArrayObject) -> (*mut c_char, *mut c_char) {
399+
let array = unsafe { _PyArray_GET_ITEM_DATA(array) };
397400
let nd = unsafe { (*array).nd } as usize;
398401
let data = unsafe { (*array).data };
399402

@@ -430,6 +433,7 @@ fn data_range<'py>(py: Python<'py>, array: *mut PyArrayObject) -> (*mut c_char,
430433
}
431434

432435
fn gcd_strides(array: *mut PyArrayObject) -> isize {
436+
let array = unsafe { _PyArray_GET_ITEM_DATA(array) };
433437
let nd = unsafe { (*array).nd } as usize;
434438

435439
if nd == 0 {
@@ -492,7 +496,7 @@ mod tests {
492496
Python::attach(|py| {
493497
let array = PyArray::<f64, _>::zeros(py, (1, 2, 3), false);
494498

495-
let base = unsafe { (*array.as_array_ptr()).base };
499+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(array.as_array_ptr().cast_const())).base };
496500
assert!(base.is_null());
497501

498502
let base_address = base_address(py, array.as_array_ptr());
@@ -509,7 +513,7 @@ mod tests {
509513
Python::attach(|py| {
510514
let array = Array::<f64, _>::zeros((1, 2, 3)).into_pyarray(py);
511515

512-
let base = unsafe { (*array.as_array_ptr()).base };
516+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(array.as_array_ptr().cast_const())).base };
513517
assert!(!base.is_null());
514518

515519
let base_address = base_address(py, array.as_array_ptr());
@@ -540,7 +544,7 @@ mod tests {
540544
array.as_ptr().cast::<c_void>()
541545
);
542546

543-
let base = unsafe { (*view.as_array_ptr()).base };
547+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(view.as_array_ptr().cast_const())).base };
544548
assert_eq!(base as *mut c_void, array.as_ptr().cast::<c_void>());
545549

546550
let base_address = base_address(py, view.as_array_ptr());
@@ -569,10 +573,10 @@ mod tests {
569573
array.as_ptr().cast::<c_void>(),
570574
);
571575

572-
let base = unsafe { (*view.as_array_ptr()).base };
576+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(view.as_array_ptr().cast_const())).base };
573577
assert_eq!(base.cast::<c_void>(), array.as_ptr().cast::<c_void>());
574578

575-
let base = unsafe { (*array.as_array_ptr()).base };
579+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(array.as_array_ptr().cast_const())).base };
576580
assert!(!base.is_null());
577581

578582
let base_address = base_address(py, view.as_array_ptr());
@@ -619,10 +623,10 @@ mod tests {
619623
view1.as_ptr().cast::<c_void>()
620624
);
621625

622-
let base = unsafe { (*view2.as_array_ptr()).base };
626+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(view2.as_array_ptr().cast_const())).base };
623627
assert_eq!(base as *mut c_void, array.as_ptr().cast::<c_void>());
624628

625-
let base = unsafe { (*view1.as_array_ptr()).base };
629+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(view1.as_array_ptr().cast_const())).base };
626630
assert_eq!(base as *mut c_void, array.as_ptr().cast::<c_void>());
627631

628632
let base_address = base_address(py, view2.as_array_ptr());
@@ -667,13 +671,13 @@ mod tests {
667671
view1.as_ptr().cast::<c_void>(),
668672
);
669673

670-
let base = unsafe { (*view2.as_array_ptr()).base };
674+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(view2.as_array_ptr().cast_const())).base };
671675
assert_eq!(base.cast::<c_void>(), array.as_ptr().cast::<c_void>());
672676

673-
let base = unsafe { (*view1.as_array_ptr()).base };
677+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(view1.as_array_ptr().cast_const())).base };
674678
assert_eq!(base.cast::<c_void>(), array.as_ptr().cast::<c_void>());
675679

676-
let base = unsafe { (*array.as_array_ptr()).base };
680+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(array.as_array_ptr().cast_const())).base };
677681
assert!(!base.is_null());
678682

679683
let base_address = base_address(py, view2.as_array_ptr());
@@ -706,7 +710,7 @@ mod tests {
706710
array.as_ptr().cast::<c_void>()
707711
);
708712

709-
let base = unsafe { (*view.as_array_ptr()).base };
713+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(view.as_array_ptr().cast_const())).base };
710714
assert_eq!(base.cast::<c_void>(), array.as_ptr().cast::<c_void>());
711715

712716
let base_address = base_address(py, view.as_array_ptr());
@@ -727,7 +731,7 @@ mod tests {
727731
Python::attach(|py| {
728732
let array = PyArray::<f64, _>::zeros(py, (1, 0, 3), false);
729733

730-
let base = unsafe { (*array.as_array_ptr()).base };
734+
let base = unsafe { (*_PyArray_GET_ITEM_DATA(array.as_array_ptr().cast_const())).base };
731735
assert!(base.is_null());
732736

733737
let base_address = base_address(py, array.as_array_ptr());

src/dtype.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ use pyo3::{
1717
};
1818

1919
use crate::npyffi::{
20-
self, NpyTypes, PyArray_Descr, PyDataType_ALIGNMENT, PyDataType_ELSIZE, PyDataType_FIELDS,
21-
PyDataType_FLAGS, PyDataType_NAMES, PyDataType_SUBARRAY, NPY_ALIGNED_STRUCT,
22-
NPY_BYTEORDER_CHAR, NPY_ITEM_HASOBJECT, NPY_TYPES, PY_ARRAY_API,
20+
self, _PyDataType_GET_ITEM_DATA, NpyTypes, PyArray_Descr, PyDataType_ALIGNMENT,
21+
PyDataType_ELSIZE, PyDataType_FIELDS, PyDataType_FLAGS, PyDataType_NAMES, PyDataType_SUBARRAY,
22+
NPY_ALIGNED_STRUCT, NPY_BYTEORDER_CHAR, NPY_ITEM_HASOBJECT, NPY_TYPES, PY_ARRAY_API,
2323
};
2424

2525
pub use num_complex::{Complex32, Complex64};
@@ -159,7 +159,7 @@ pub trait PyArrayDescrMethods<'py>: Sealed {
159159
/// [enumerated-types]: https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types
160160
/// [dtype-num]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.num.html
161161
fn num(&self) -> c_int {
162-
unsafe { &*self.as_dtype_ptr() }.type_num
162+
unsafe { &*_PyDataType_GET_ITEM_DATA(self.as_dtype_ptr()) }.type_num
163163
}
164164

165165
/// Returns the element size of this type descriptor.
@@ -184,7 +184,9 @@ pub trait PyArrayDescrMethods<'py>: Sealed {
184184
///
185185
/// [dtype-byteorder]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.byteorder.html
186186
fn byteorder(&self) -> u8 {
187-
unsafe { &*self.as_dtype_ptr() }.byteorder.max(0) as _
187+
unsafe { &*_PyDataType_GET_ITEM_DATA(self.as_dtype_ptr()) }
188+
.byteorder
189+
.max(0) as _
188190
}
189191

190192
/// Returns a unique ASCII character for each of the 21 different built-in types.
@@ -195,7 +197,9 @@ pub trait PyArrayDescrMethods<'py>: Sealed {
195197
///
196198
/// [dtype-char]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.char.html
197199
fn char(&self) -> u8 {
198-
unsafe { &*self.as_dtype_ptr() }.type_.max(0) as _
200+
unsafe { &*_PyDataType_GET_ITEM_DATA(self.as_dtype_ptr()) }
201+
.type_
202+
.max(0) as _
199203
}
200204

201205
/// Returns an ASCII character (one of `biufcmMOSUV`) identifying the general kind of data.
@@ -206,7 +210,9 @@ pub trait PyArrayDescrMethods<'py>: Sealed {
206210
///
207211
/// [dtype-kind]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.kind.html
208212
fn kind(&self) -> u8 {
209-
unsafe { &*self.as_dtype_ptr() }.kind.max(0) as _
213+
unsafe { &*_PyDataType_GET_ITEM_DATA(self.as_dtype_ptr()) }
214+
.kind
215+
.max(0) as _
210216
}
211217

212218
/// Returns bit-flags describing how this type descriptor is to be interpreted.
@@ -330,7 +336,7 @@ impl<'py> PyArrayDescrMethods<'py> for Bound<'py, PyArrayDescr> {
330336
}
331337

332338
fn typeobj(&self) -> Bound<'py, PyType> {
333-
let dtype_type_ptr = unsafe { &*self.as_dtype_ptr() }.typeobj;
339+
let dtype_type_ptr = unsafe { &*_PyDataType_GET_ITEM_DATA(self.as_dtype_ptr()) }.typeobj;
334340
unsafe { PyType::from_borrowed_type_ptr(self.py(), dtype_type_ptr) }
335341
}
336342

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,6 @@ macro_rules! pyarray {
181181
$crate::IntoPyArray::into_pyarray($crate::array![$($x,)*], $py)
182182
}};
183183
}
184+
185+
#[cfg(all(not(Py_3_15), Py_GIL_DISABLED, target_pointer_width = "32"))]
186+
compile_error!("free-threaded Python on 32bit platforms is only supported from 3.15+");

src/npyffi/array.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ impl PyArrayAPI {
301301
impl_api![189; PyArray_LexSort(sort_keys: *mut PyObject, axis: c_int) -> *mut PyObject];
302302
impl_api![190; PyArray_Round(a: *mut PyArrayObject, decimals: c_int, out: *mut PyArrayObject) -> *mut PyObject];
303303
impl_api![191; PyArray_EquivTypenums(typenum1: c_int, typenum2: c_int) -> c_uchar];
304+
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
304305
impl_api![192; PyArray_RegisterDataType(descr: *mut PyArray_DescrProto) -> c_int];
305306
impl_api![193; PyArray_RegisterCastFunc(descr: *mut PyArray_Descr, totype: c_int, castfunc: PyArray_VectorUnaryFunc) -> c_int];
306307
impl_api![194; PyArray_RegisterCanCast(descr: *mut PyArray_Descr, totype: c_int, scalar: NPY_SCALARKIND) -> c_int];
@@ -428,6 +429,22 @@ impl PyArrayAPI {
428429
// Min v2.0 impl_api![363; PyArray_CommonDType(dtype1: *mut PyArray_DTypeMeta, dtype2: *mut PyArray_DTypeMeta) -> PyArray_DTypeMeta];
429430
// Min v2.0 impl_api![364; PyArray_PromoteDTypeSequence(length: npy_intp, dtypes_in: *mut *mut PyArray_DTypeMeta) -> *mut PyArray_DTypeMeta];
430431
// Min v2.0 impl_api![365; _PyDataType_GetArrFuncs(descr: *const PyArray_Descr) -> *mut PyArray_ArrFuncs];
432+
// Slot 366, 367, 368 are the abstract DTypes
433+
}
434+
435+
// Min v2.5
436+
#[cfg(all(Py_LIMITED_API, Py_GIL_DISABLED))]
437+
impl PyArrayAPI {
438+
impl_api![369; _PyArray_GET_ITEM_DATA(arr: *const PyArrayObject) -> *mut PyArrayObject_fields];
439+
impl_api![370; _PyArrayIter_GET_ITEM_DATA(iter: *const PyArrayIterObject) -> *mut PyArrayIterObject_fields];
440+
impl_api![371; pub(crate) _PyArray_LegacyDescr_GET_ITEM_DATA(dtype: *const _PyArray_LegacyDescr) -> *mut _PyArray_LegacyDescr_fields];
441+
impl_api![372; _PyDataType_GET_ITEM_DATA(dtype: *const PyArray_Descr) -> *mut PyArray_Descr_fields];
442+
impl_api![373; _PyArrayMultiIter_GET_ITEM_DATA(multi: *const PyArrayMultiIterObject) -> *mut PyArrayMultiIterObject_fields];
443+
impl_api![374; _PyArrayNeighborhoodIter_GET_ITEM_DATA(iter: *const PyArrayNeighborhoodIterObject) -> *mut PyArrayNeighborhoodIterObject_fields];
444+
impl_api![375; _PyDatetimeScalarObject_GetMetadata(obj: *mut PyObject) -> PyArray_DatetimeMetaData];
445+
impl_api![376; _PyTimedeltaScalarObject_GetMetadata(obj: *mut PyObject) -> PyArray_DatetimeMetaData];
446+
impl_api![377; _PyDatetimeScalarObject_GetValue(obj: *mut PyObject) -> npy_datetime];
447+
impl_api![378; _PyTimedeltaScalarObject_GetValue(obj: *mut PyObject) -> npy_timedelta];
431448
}
432449

433450
/// Checks that `op` is an instance of `PyArray` or not.

src/npyffi/mod.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,15 @@ pub fn is_numpy_2<'py>(py: Python<'py>) -> bool {
5858
macro_rules! impl_api {
5959
// API available on all versions
6060
[$offset: expr; $fname: ident ($($arg: ident: $t: ty),* $(,)?) $(-> $ret: ty)?] => {
61+
impl_api![$offset; pub $fname($($arg : $t), *) $(-> $ret)*];
62+
};
63+
[$offset: expr; $vis:vis $fname: ident ($($arg: ident: $t: ty),* $(,)?) $(-> $ret: ty)?] => {
6164
#[allow(non_snake_case)]
62-
pub unsafe fn $fname<'py>(&self, py: Python<'py>, $($arg : $t), *) $(-> $ret)* {
65+
$vis unsafe fn $fname<'py>(&self, py: Python<'py>, $($arg : $t), *) $(-> $ret)* {
6366
let f: extern "C" fn ($($arg : $t), *) $(-> $ret)* = self.get(py, $offset).cast().read();
6467
f($($arg), *)
6568
}
66-
};
69+
}
6770
}
6871

6972
// Define type objects associated with the NumPy API
@@ -82,6 +85,21 @@ macro_rules! impl_array_type {
8285
}
8386
}
8487

88+
// Until `extern type` is stabilized, use the recommended approach to
89+
// model opaque types:
90+
// https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs
91+
#[cfg(all(Py_LIMITED_API, Py_GIL_DISABLED))]
92+
macro_rules! opaque_struct {
93+
($(#[$attrs:meta])* $pub:vis $name:ident) => {
94+
$(#[$attrs])*
95+
#[repr(C)]
96+
$pub struct $name {
97+
_data: (),
98+
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
99+
}
100+
};
101+
}
102+
85103
impl_array_type! {
86104
// Multiarray API
87105
// Slot 1 was never meaningfully used by NumPy

0 commit comments

Comments
 (0)