pyo3/
buffer.rs

1#![cfg(any(not(Py_LIMITED_API), Py_3_11))]
2// Copyright (c) 2017 Daniel Grunwald
3//
4// Permission is hereby granted, free of charge, to any person obtaining a copy of this
5// software and associated documentation files (the "Software"), to deal in the Software
6// without restriction, including without limitation the rights to use, copy, modify, merge,
7// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8// to whom the Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all copies or
11// substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18// DEALINGS IN THE SOFTWARE.
19
20//! `PyBuffer` implementation
21use crate::Bound;
22use crate::{err, exceptions::PyBufferError, ffi, FromPyObject, PyAny, PyResult, Python};
23use std::ffi::{
24    c_char, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, c_ulonglong,
25    c_ushort, c_void,
26};
27use std::marker::PhantomData;
28use std::pin::Pin;
29use std::{cell, mem, ptr, slice};
30use std::{ffi::CStr, fmt::Debug};
31
32/// Allows access to the underlying buffer used by a python object such as `bytes`, `bytearray` or `array.array`.
33// use Pin<Box> because Python expects that the Py_buffer struct has a stable memory address
34#[repr(transparent)]
35pub struct PyBuffer<T>(Pin<Box<ffi::Py_buffer>>, PhantomData<T>);
36
37// PyBuffer is thread-safe: the shape of the buffer is immutable while a Py_buffer exists.
38// Accessing the buffer contents is protected using the GIL.
39unsafe impl<T> Send for PyBuffer<T> {}
40unsafe impl<T> Sync for PyBuffer<T> {}
41
42impl<T> Debug for PyBuffer<T> {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        f.debug_struct("PyBuffer")
45            .field("buf", &self.0.buf)
46            .field("obj", &self.0.obj)
47            .field("len", &self.0.len)
48            .field("itemsize", &self.0.itemsize)
49            .field("readonly", &self.0.readonly)
50            .field("ndim", &self.0.ndim)
51            .field("format", &self.0.format)
52            .field("shape", &self.0.shape)
53            .field("strides", &self.0.strides)
54            .field("suboffsets", &self.0.suboffsets)
55            .field("internal", &self.0.internal)
56            .finish()
57    }
58}
59
60/// Represents the type of a Python buffer element.
61#[derive(Copy, Clone, Debug, Eq, PartialEq)]
62pub enum ElementType {
63    /// A signed integer type.
64    SignedInteger {
65        /// The width of the signed integer in bytes.
66        bytes: usize,
67    },
68    /// An unsigned integer type.
69    UnsignedInteger {
70        /// The width of the unsigned integer in bytes.
71        bytes: usize,
72    },
73    /// A boolean type.
74    Bool,
75    /// A float type.
76    Float {
77        /// The width of the float in bytes.
78        bytes: usize,
79    },
80    /// An unknown type. This may occur when parsing has failed.
81    Unknown,
82}
83
84impl ElementType {
85    /// Determines the `ElementType` from a Python `struct` module format string.
86    ///
87    /// See <https://docs.python.org/3/library/struct.html#format-strings> for more information
88    /// about struct format strings.
89    pub fn from_format(format: &CStr) -> ElementType {
90        match format.to_bytes() {
91            [size] | [b'@', size] => native_element_type_from_type_char(*size),
92            [b'=' | b'<' | b'>' | b'!', size] => standard_element_type_from_type_char(*size),
93            _ => ElementType::Unknown,
94        }
95    }
96}
97
98fn native_element_type_from_type_char(type_char: u8) -> ElementType {
99    use self::ElementType::*;
100    match type_char {
101        b'c' => UnsignedInteger {
102            bytes: mem::size_of::<c_char>(),
103        },
104        b'b' => SignedInteger {
105            bytes: mem::size_of::<c_schar>(),
106        },
107        b'B' => UnsignedInteger {
108            bytes: mem::size_of::<c_uchar>(),
109        },
110        b'?' => Bool,
111        b'h' => SignedInteger {
112            bytes: mem::size_of::<c_short>(),
113        },
114        b'H' => UnsignedInteger {
115            bytes: mem::size_of::<c_ushort>(),
116        },
117        b'i' => SignedInteger {
118            bytes: mem::size_of::<c_int>(),
119        },
120        b'I' => UnsignedInteger {
121            bytes: mem::size_of::<c_uint>(),
122        },
123        b'l' => SignedInteger {
124            bytes: mem::size_of::<c_long>(),
125        },
126        b'L' => UnsignedInteger {
127            bytes: mem::size_of::<c_ulong>(),
128        },
129        b'q' => SignedInteger {
130            bytes: mem::size_of::<c_longlong>(),
131        },
132        b'Q' => UnsignedInteger {
133            bytes: mem::size_of::<c_ulonglong>(),
134        },
135        b'n' => SignedInteger {
136            bytes: mem::size_of::<libc::ssize_t>(),
137        },
138        b'N' => UnsignedInteger {
139            bytes: mem::size_of::<libc::size_t>(),
140        },
141        b'e' => Float { bytes: 2 },
142        b'f' => Float { bytes: 4 },
143        b'd' => Float { bytes: 8 },
144        _ => Unknown,
145    }
146}
147
148fn standard_element_type_from_type_char(type_char: u8) -> ElementType {
149    use self::ElementType::*;
150    match type_char {
151        b'c' | b'B' => UnsignedInteger { bytes: 1 },
152        b'b' => SignedInteger { bytes: 1 },
153        b'?' => Bool,
154        b'h' => SignedInteger { bytes: 2 },
155        b'H' => UnsignedInteger { bytes: 2 },
156        b'i' | b'l' => SignedInteger { bytes: 4 },
157        b'I' | b'L' => UnsignedInteger { bytes: 4 },
158        b'q' => SignedInteger { bytes: 8 },
159        b'Q' => UnsignedInteger { bytes: 8 },
160        b'e' => Float { bytes: 2 },
161        b'f' => Float { bytes: 4 },
162        b'd' => Float { bytes: 8 },
163        _ => Unknown,
164    }
165}
166
167#[cfg(target_endian = "little")]
168fn is_matching_endian(c: u8) -> bool {
169    c == b'@' || c == b'=' || c == b'>'
170}
171
172#[cfg(target_endian = "big")]
173fn is_matching_endian(c: u8) -> bool {
174    c == b'@' || c == b'=' || c == b'>' || c == b'!'
175}
176
177/// Trait implemented for possible element types of `PyBuffer`.
178///
179/// # Safety
180///
181/// This trait must only be implemented for types which represent valid elements of Python buffers.
182pub unsafe trait Element: Copy {
183    /// Gets whether the element specified in the format string is potentially compatible.
184    /// Alignment and size are checked separately from this function.
185    fn is_compatible_format(format: &CStr) -> bool;
186}
187
188impl<T: Element> FromPyObject<'_> for PyBuffer<T> {
189    fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<PyBuffer<T>> {
190        Self::get(obj)
191    }
192}
193
194impl<T: Element> PyBuffer<T> {
195    /// Gets the underlying buffer from the specified python object.
196    pub fn get(obj: &Bound<'_, PyAny>) -> PyResult<PyBuffer<T>> {
197        // TODO: use nightly API Box::new_uninit() once our MSRV is 1.82
198        let mut buf = Box::new(mem::MaybeUninit::uninit());
199        let buf: Box<ffi::Py_buffer> = {
200            err::error_on_minusone(obj.py(), unsafe {
201                ffi::PyObject_GetBuffer(obj.as_ptr(), buf.as_mut_ptr(), ffi::PyBUF_FULL_RO)
202            })?;
203            // Safety: buf is initialized by PyObject_GetBuffer.
204            // TODO: use nightly API Box::assume_init() once our MSRV is 1.82
205            unsafe { mem::transmute(buf) }
206        };
207        // Create PyBuffer immediately so that if validation checks fail, the PyBuffer::drop code
208        // will call PyBuffer_Release (thus avoiding any leaks).
209        let buf = PyBuffer(Pin::from(buf), PhantomData);
210
211        if buf.0.shape.is_null() {
212            Err(PyBufferError::new_err("shape is null"))
213        } else if buf.0.strides.is_null() {
214            Err(PyBufferError::new_err("strides is null"))
215        } else if mem::size_of::<T>() != buf.item_size() || !T::is_compatible_format(buf.format()) {
216            Err(PyBufferError::new_err(format!(
217                "buffer contents are not compatible with {}",
218                std::any::type_name::<T>()
219            )))
220        } else if buf.0.buf.align_offset(mem::align_of::<T>()) != 0 {
221            Err(PyBufferError::new_err(format!(
222                "buffer contents are insufficiently aligned for {}",
223                std::any::type_name::<T>()
224            )))
225        } else {
226            Ok(buf)
227        }
228    }
229
230    /// Gets the pointer to the start of the buffer memory.
231    ///
232    /// Warning: the buffer memory can be mutated by other code (including
233    /// other Python functions, if the GIL is released, or other extension
234    /// modules even if the GIL is held). You must either access memory
235    /// atomically, or ensure there are no data races yourself. See
236    /// [this blog post] for more details.
237    ///
238    /// [this blog post]: https://alexgaynor.net/2022/oct/23/buffers-on-the-edge/
239    #[inline]
240    pub fn buf_ptr(&self) -> *mut c_void {
241        self.0.buf
242    }
243
244    /// Gets a pointer to the specified item.
245    ///
246    /// If `indices.len() < self.dimensions()`, returns the start address of the sub-array at the specified dimension.
247    pub fn get_ptr(&self, indices: &[usize]) -> *mut c_void {
248        let shape = &self.shape()[..indices.len()];
249        for i in 0..indices.len() {
250            assert!(indices[i] < shape[i]);
251        }
252        unsafe {
253            ffi::PyBuffer_GetPointer(
254                #[cfg(Py_3_11)]
255                &*self.0,
256                #[cfg(not(Py_3_11))]
257                {
258                    &*self.0 as *const ffi::Py_buffer as *mut ffi::Py_buffer
259                },
260                #[cfg(Py_3_11)]
261                {
262                    indices.as_ptr().cast()
263                },
264                #[cfg(not(Py_3_11))]
265                {
266                    indices.as_ptr() as *mut ffi::Py_ssize_t
267                },
268            )
269        }
270    }
271
272    /// Gets whether the underlying buffer is read-only.
273    #[inline]
274    pub fn readonly(&self) -> bool {
275        self.0.readonly != 0
276    }
277
278    /// Gets the size of a single element, in bytes.
279    /// Important exception: when requesting an unformatted buffer, item_size still has the value
280    #[inline]
281    pub fn item_size(&self) -> usize {
282        self.0.itemsize as usize
283    }
284
285    /// Gets the total number of items.
286    #[inline]
287    pub fn item_count(&self) -> usize {
288        (self.0.len as usize) / (self.0.itemsize as usize)
289    }
290
291    /// `item_size() * item_count()`.
292    /// For contiguous arrays, this is the length of the underlying memory block.
293    /// For non-contiguous arrays, it is the length that the logical structure would have if it were copied to a contiguous representation.
294    #[inline]
295    pub fn len_bytes(&self) -> usize {
296        self.0.len as usize
297    }
298
299    /// Gets the number of dimensions.
300    ///
301    /// May be 0 to indicate a single scalar value.
302    #[inline]
303    pub fn dimensions(&self) -> usize {
304        self.0.ndim as usize
305    }
306
307    /// Returns an array of length `dimensions`. `shape()[i]` is the length of the array in dimension number `i`.
308    ///
309    /// May return None for single-dimensional arrays or scalar values (`dimensions() <= 1`);
310    /// You can call `item_count()` to get the length of the single dimension.
311    ///
312    /// Despite Python using an array of signed integers, the values are guaranteed to be non-negative.
313    /// However, dimensions of length 0 are possible and might need special attention.
314    #[inline]
315    pub fn shape(&self) -> &[usize] {
316        unsafe { slice::from_raw_parts(self.0.shape.cast(), self.0.ndim as usize) }
317    }
318
319    /// Returns an array that holds, for each dimension, the number of bytes to skip to get to the next element in the dimension.
320    ///
321    /// Stride values can be any integer. For regular arrays, strides are usually positive,
322    /// but a consumer MUST be able to handle the case `strides[n] <= 0`.
323    #[inline]
324    pub fn strides(&self) -> &[isize] {
325        unsafe { slice::from_raw_parts(self.0.strides, self.0.ndim as usize) }
326    }
327
328    /// An array of length ndim.
329    /// If `suboffsets[n] >= 0`, the values stored along the nth dimension are pointers and the suboffset value dictates how many bytes to add to each pointer after de-referencing.
330    /// A suboffset value that is negative indicates that no de-referencing should occur (striding in a contiguous memory block).
331    ///
332    /// If all suboffsets are negative (i.e. no de-referencing is needed), then this field must be NULL (the default value).
333    #[inline]
334    pub fn suboffsets(&self) -> Option<&[isize]> {
335        unsafe {
336            if self.0.suboffsets.is_null() {
337                None
338            } else {
339                Some(slice::from_raw_parts(
340                    self.0.suboffsets,
341                    self.0.ndim as usize,
342                ))
343            }
344        }
345    }
346
347    /// A NUL terminated string in struct module style syntax describing the contents of a single item.
348    #[inline]
349    pub fn format(&self) -> &CStr {
350        if self.0.format.is_null() {
351            ffi::c_str!("B")
352        } else {
353            unsafe { CStr::from_ptr(self.0.format) }
354        }
355    }
356
357    /// Gets whether the buffer is contiguous in C-style order (last index varies fastest when visiting items in order of memory address).
358    #[inline]
359    pub fn is_c_contiguous(&self) -> bool {
360        unsafe { ffi::PyBuffer_IsContiguous(&*self.0, b'C' as std::ffi::c_char) != 0 }
361    }
362
363    /// Gets whether the buffer is contiguous in Fortran-style order (first index varies fastest when visiting items in order of memory address).
364    #[inline]
365    pub fn is_fortran_contiguous(&self) -> bool {
366        unsafe { ffi::PyBuffer_IsContiguous(&*self.0, b'F' as std::ffi::c_char) != 0 }
367    }
368
369    /// Gets the buffer memory as a slice.
370    ///
371    /// This function succeeds if:
372    /// * the buffer format is compatible with `T`
373    /// * alignment and size of buffer elements is matching the expectations for type `T`
374    /// * the buffer is C-style contiguous
375    ///
376    /// The returned slice uses type `Cell<T>` because it's theoretically possible for any call into the Python runtime
377    /// to modify the values in the slice.
378    pub fn as_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [ReadOnlyCell<T>]> {
379        if self.is_c_contiguous() {
380            unsafe {
381                Some(slice::from_raw_parts(
382                    self.0.buf as *mut ReadOnlyCell<T>,
383                    self.item_count(),
384                ))
385            }
386        } else {
387            None
388        }
389    }
390
391    /// Gets the buffer memory as a slice.
392    ///
393    /// This function succeeds if:
394    /// * the buffer is not read-only
395    /// * the buffer format is compatible with `T`
396    /// * alignment and size of buffer elements is matching the expectations for type `T`
397    /// * the buffer is C-style contiguous
398    ///
399    /// The returned slice uses type `Cell<T>` because it's theoretically possible for any call into the Python runtime
400    /// to modify the values in the slice.
401    pub fn as_mut_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [cell::Cell<T>]> {
402        if !self.readonly() && self.is_c_contiguous() {
403            unsafe {
404                Some(slice::from_raw_parts(
405                    self.0.buf as *mut cell::Cell<T>,
406                    self.item_count(),
407                ))
408            }
409        } else {
410            None
411        }
412    }
413
414    /// Gets the buffer memory as a slice.
415    ///
416    /// This function succeeds if:
417    /// * the buffer format is compatible with `T`
418    /// * alignment and size of buffer elements is matching the expectations for type `T`
419    /// * the buffer is Fortran-style contiguous
420    ///
421    /// The returned slice uses type `Cell<T>` because it's theoretically possible for any call into the Python runtime
422    /// to modify the values in the slice.
423    pub fn as_fortran_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [ReadOnlyCell<T>]> {
424        if mem::size_of::<T>() == self.item_size() && self.is_fortran_contiguous() {
425            unsafe {
426                Some(slice::from_raw_parts(
427                    self.0.buf as *mut ReadOnlyCell<T>,
428                    self.item_count(),
429                ))
430            }
431        } else {
432            None
433        }
434    }
435
436    /// Gets the buffer memory as a slice.
437    ///
438    /// This function succeeds if:
439    /// * the buffer is not read-only
440    /// * the buffer format is compatible with `T`
441    /// * alignment and size of buffer elements is matching the expectations for type `T`
442    /// * the buffer is Fortran-style contiguous
443    ///
444    /// The returned slice uses type `Cell<T>` because it's theoretically possible for any call into the Python runtime
445    /// to modify the values in the slice.
446    pub fn as_fortran_mut_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [cell::Cell<T>]> {
447        if !self.readonly() && self.is_fortran_contiguous() {
448            unsafe {
449                Some(slice::from_raw_parts(
450                    self.0.buf as *mut cell::Cell<T>,
451                    self.item_count(),
452                ))
453            }
454        } else {
455            None
456        }
457    }
458
459    /// Copies the buffer elements to the specified slice.
460    /// If the buffer is multi-dimensional, the elements are written in C-style order.
461    ///
462    ///  * Fails if the slice does not have the correct length (`buf.item_count()`).
463    ///  * Fails if the buffer format is not compatible with type `T`.
464    ///
465    /// To check whether the buffer format is compatible before calling this method,
466    /// you can use `<T as buffer::Element>::is_compatible_format(buf.format())`.
467    /// Alternatively, `match buffer::ElementType::from_format(buf.format())`.
468    pub fn copy_to_slice(&self, py: Python<'_>, target: &mut [T]) -> PyResult<()> {
469        self._copy_to_slice(py, target, b'C')
470    }
471
472    /// Copies the buffer elements to the specified slice.
473    /// If the buffer is multi-dimensional, the elements are written in Fortran-style order.
474    ///
475    ///  * Fails if the slice does not have the correct length (`buf.item_count()`).
476    ///  * Fails if the buffer format is not compatible with type `T`.
477    ///
478    /// To check whether the buffer format is compatible before calling this method,
479    /// you can use `<T as buffer::Element>::is_compatible_format(buf.format())`.
480    /// Alternatively, `match buffer::ElementType::from_format(buf.format())`.
481    pub fn copy_to_fortran_slice(&self, py: Python<'_>, target: &mut [T]) -> PyResult<()> {
482        self._copy_to_slice(py, target, b'F')
483    }
484
485    fn _copy_to_slice(&self, py: Python<'_>, target: &mut [T], fort: u8) -> PyResult<()> {
486        if mem::size_of_val(target) != self.len_bytes() {
487            return Err(PyBufferError::new_err(format!(
488                "slice to copy to (of length {}) does not match buffer length of {}",
489                target.len(),
490                self.item_count()
491            )));
492        }
493
494        err::error_on_minusone(py, unsafe {
495            ffi::PyBuffer_ToContiguous(
496                target.as_mut_ptr().cast(),
497                #[cfg(Py_3_11)]
498                &*self.0,
499                #[cfg(not(Py_3_11))]
500                {
501                    &*self.0 as *const ffi::Py_buffer as *mut ffi::Py_buffer
502                },
503                self.0.len,
504                fort as std::ffi::c_char,
505            )
506        })
507    }
508
509    /// Copies the buffer elements to a newly allocated vector.
510    /// If the buffer is multi-dimensional, the elements are written in C-style order.
511    ///
512    /// Fails if the buffer format is not compatible with type `T`.
513    pub fn to_vec(&self, py: Python<'_>) -> PyResult<Vec<T>> {
514        self._to_vec(py, b'C')
515    }
516
517    /// Copies the buffer elements to a newly allocated vector.
518    /// If the buffer is multi-dimensional, the elements are written in Fortran-style order.
519    ///
520    /// Fails if the buffer format is not compatible with type `T`.
521    pub fn to_fortran_vec(&self, py: Python<'_>) -> PyResult<Vec<T>> {
522        self._to_vec(py, b'F')
523    }
524
525    fn _to_vec(&self, py: Python<'_>, fort: u8) -> PyResult<Vec<T>> {
526        let item_count = self.item_count();
527        let mut vec: Vec<T> = Vec::with_capacity(item_count);
528
529        // Copy the buffer into the uninitialized space in the vector.
530        // Due to T:Copy, we don't need to be concerned with Drop impls.
531        err::error_on_minusone(py, unsafe {
532            ffi::PyBuffer_ToContiguous(
533                vec.as_ptr() as *mut c_void,
534                #[cfg(Py_3_11)]
535                &*self.0,
536                #[cfg(not(Py_3_11))]
537                {
538                    &*self.0 as *const ffi::Py_buffer as *mut ffi::Py_buffer
539                },
540                self.0.len,
541                fort as std::ffi::c_char,
542            )
543        })?;
544        // set vector length to mark the now-initialized space as usable
545        unsafe { vec.set_len(item_count) };
546        Ok(vec)
547    }
548
549    /// Copies the specified slice into the buffer.
550    /// If the buffer is multi-dimensional, the elements in the slice are expected to be in C-style order.
551    ///
552    ///  * Fails if the buffer is read-only.
553    ///  * Fails if the slice does not have the correct length (`buf.item_count()`).
554    ///  * Fails if the buffer format is not compatible with type `T`.
555    ///
556    /// To check whether the buffer format is compatible before calling this method,
557    /// use `<T as buffer::Element>::is_compatible_format(buf.format())`.
558    /// Alternatively, `match buffer::ElementType::from_format(buf.format())`.
559    pub fn copy_from_slice(&self, py: Python<'_>, source: &[T]) -> PyResult<()> {
560        self._copy_from_slice(py, source, b'C')
561    }
562
563    /// Copies the specified slice into the buffer.
564    /// If the buffer is multi-dimensional, the elements in the slice are expected to be in Fortran-style order.
565    ///
566    ///  * Fails if the buffer is read-only.
567    ///  * Fails if the slice does not have the correct length (`buf.item_count()`).
568    ///  * Fails if the buffer format is not compatible with type `T`.
569    ///
570    /// To check whether the buffer format is compatible before calling this method,
571    /// use `<T as buffer::Element>::is_compatible_format(buf.format())`.
572    /// Alternatively, `match buffer::ElementType::from_format(buf.format())`.
573    pub fn copy_from_fortran_slice(&self, py: Python<'_>, source: &[T]) -> PyResult<()> {
574        self._copy_from_slice(py, source, b'F')
575    }
576
577    fn _copy_from_slice(&self, py: Python<'_>, source: &[T], fort: u8) -> PyResult<()> {
578        if self.readonly() {
579            return Err(PyBufferError::new_err("cannot write to read-only buffer"));
580        } else if mem::size_of_val(source) != self.len_bytes() {
581            return Err(PyBufferError::new_err(format!(
582                "slice to copy from (of length {}) does not match buffer length of {}",
583                source.len(),
584                self.item_count()
585            )));
586        }
587
588        err::error_on_minusone(py, unsafe {
589            ffi::PyBuffer_FromContiguous(
590                #[cfg(Py_3_11)]
591                &*self.0,
592                #[cfg(not(Py_3_11))]
593                {
594                    &*self.0 as *const ffi::Py_buffer as *mut ffi::Py_buffer
595                },
596                #[cfg(Py_3_11)]
597                {
598                    source.as_ptr().cast()
599                },
600                #[cfg(not(Py_3_11))]
601                {
602                    source.as_ptr() as *mut c_void
603                },
604                self.0.len,
605                fort as std::ffi::c_char,
606            )
607        })
608    }
609
610    /// Releases the buffer object, freeing the reference to the Python object
611    /// which owns the buffer.
612    ///
613    /// This will automatically be called on drop.
614    pub fn release(self, _py: Python<'_>) {
615        // First move self into a ManuallyDrop, so that PyBuffer::drop will
616        // never be called. (It would acquire the GIL and call PyBuffer_Release
617        // again.)
618        let mut mdself = mem::ManuallyDrop::new(self);
619        unsafe {
620            // Next, make the actual PyBuffer_Release call.
621            ffi::PyBuffer_Release(&mut *mdself.0);
622
623            // Finally, drop the contained Pin<Box<_>> in place, to free the
624            // Box memory.
625            let inner: *mut Pin<Box<ffi::Py_buffer>> = &mut mdself.0;
626            ptr::drop_in_place(inner);
627        }
628    }
629}
630
631impl<T> Drop for PyBuffer<T> {
632    fn drop(&mut self) {
633        fn inner(buf: &mut Pin<Box<ffi::Py_buffer>>) {
634            if Python::try_attach(|_| unsafe { ffi::PyBuffer_Release(&mut **buf) }).is_none()
635                && crate::internal::state::is_in_gc_traversal()
636            {
637                eprintln!("Warning: PyBuffer dropped while in GC traversal, this is a bug and will leak memory.");
638            }
639            // If `try_attach` failed and `is_in_gc_traversal()` is false, then probably the interpreter has
640            // already finalized and we can just assume that the underlying memory has already been freed.
641            //
642            // So we don't handle that case here.
643        }
644
645        inner(&mut self.0);
646    }
647}
648
649/// Like [std::cell::Cell], but only provides read-only access to the data.
650///
651/// `&ReadOnlyCell<T>` is basically a safe version of `*const T`:
652///  The data cannot be modified through the reference, but other references may
653///  be modifying the data.
654#[repr(transparent)]
655pub struct ReadOnlyCell<T: Element>(cell::UnsafeCell<T>);
656
657impl<T: Element> ReadOnlyCell<T> {
658    /// Returns a copy of the current value.
659    #[inline]
660    pub fn get(&self) -> T {
661        unsafe { *self.0.get() }
662    }
663
664    /// Returns a pointer to the current value.
665    #[inline]
666    pub fn as_ptr(&self) -> *const T {
667        self.0.get()
668    }
669}
670
671macro_rules! impl_element(
672    ($t:ty, $f:ident) => {
673        unsafe impl Element for $t {
674            fn is_compatible_format(format: &CStr) -> bool {
675                let slice = format.to_bytes();
676                if slice.len() > 1 && !is_matching_endian(slice[0]) {
677                    return false;
678                }
679                ElementType::from_format(format) == ElementType::$f { bytes: mem::size_of::<$t>() }
680            }
681        }
682    }
683);
684
685impl_element!(u8, UnsignedInteger);
686impl_element!(u16, UnsignedInteger);
687impl_element!(u32, UnsignedInteger);
688impl_element!(u64, UnsignedInteger);
689impl_element!(usize, UnsignedInteger);
690impl_element!(i8, SignedInteger);
691impl_element!(i16, SignedInteger);
692impl_element!(i32, SignedInteger);
693impl_element!(i64, SignedInteger);
694impl_element!(isize, SignedInteger);
695impl_element!(f32, Float);
696impl_element!(f64, Float);
697
698#[cfg(test)]
699mod tests {
700    use super::*;
701
702    use crate::ffi;
703    use crate::types::any::PyAnyMethods;
704    use crate::Python;
705
706    #[test]
707    fn test_debug() {
708        Python::attach(|py| {
709            let bytes = py.eval(ffi::c_str!("b'abcde'"), None, None).unwrap();
710            let buffer: PyBuffer<u8> = PyBuffer::get(&bytes).unwrap();
711            let expected = format!(
712                concat!(
713                    "PyBuffer {{ buf: {:?}, obj: {:?}, ",
714                    "len: 5, itemsize: 1, readonly: 1, ",
715                    "ndim: 1, format: {:?}, shape: {:?}, ",
716                    "strides: {:?}, suboffsets: {:?}, internal: {:?} }}",
717                ),
718                buffer.0.buf,
719                buffer.0.obj,
720                buffer.0.format,
721                buffer.0.shape,
722                buffer.0.strides,
723                buffer.0.suboffsets,
724                buffer.0.internal
725            );
726            let debug_repr = format!("{buffer:?}");
727            assert_eq!(debug_repr, expected);
728        });
729    }
730
731    #[test]
732    fn test_element_type_from_format() {
733        use super::ElementType::*;
734        use std::mem::size_of;
735
736        for (cstr, expected) in [
737            // @ prefix goes to native_element_type_from_type_char
738            (
739                ffi::c_str!("@b"),
740                SignedInteger {
741                    bytes: size_of::<c_schar>(),
742                },
743            ),
744            (
745                ffi::c_str!("@c"),
746                UnsignedInteger {
747                    bytes: size_of::<c_char>(),
748                },
749            ),
750            (
751                ffi::c_str!("@b"),
752                SignedInteger {
753                    bytes: size_of::<c_schar>(),
754                },
755            ),
756            (
757                ffi::c_str!("@B"),
758                UnsignedInteger {
759                    bytes: size_of::<c_uchar>(),
760                },
761            ),
762            (ffi::c_str!("@?"), Bool),
763            (
764                ffi::c_str!("@h"),
765                SignedInteger {
766                    bytes: size_of::<c_short>(),
767                },
768            ),
769            (
770                ffi::c_str!("@H"),
771                UnsignedInteger {
772                    bytes: size_of::<c_ushort>(),
773                },
774            ),
775            (
776                ffi::c_str!("@i"),
777                SignedInteger {
778                    bytes: size_of::<c_int>(),
779                },
780            ),
781            (
782                ffi::c_str!("@I"),
783                UnsignedInteger {
784                    bytes: size_of::<c_uint>(),
785                },
786            ),
787            (
788                ffi::c_str!("@l"),
789                SignedInteger {
790                    bytes: size_of::<c_long>(),
791                },
792            ),
793            (
794                ffi::c_str!("@L"),
795                UnsignedInteger {
796                    bytes: size_of::<c_ulong>(),
797                },
798            ),
799            (
800                ffi::c_str!("@q"),
801                SignedInteger {
802                    bytes: size_of::<c_longlong>(),
803                },
804            ),
805            (
806                ffi::c_str!("@Q"),
807                UnsignedInteger {
808                    bytes: size_of::<c_ulonglong>(),
809                },
810            ),
811            (
812                ffi::c_str!("@n"),
813                SignedInteger {
814                    bytes: size_of::<libc::ssize_t>(),
815                },
816            ),
817            (
818                ffi::c_str!("@N"),
819                UnsignedInteger {
820                    bytes: size_of::<libc::size_t>(),
821                },
822            ),
823            (ffi::c_str!("@e"), Float { bytes: 2 }),
824            (ffi::c_str!("@f"), Float { bytes: 4 }),
825            (ffi::c_str!("@d"), Float { bytes: 8 }),
826            (ffi::c_str!("@z"), Unknown),
827            // = prefix goes to standard_element_type_from_type_char
828            (ffi::c_str!("=b"), SignedInteger { bytes: 1 }),
829            (ffi::c_str!("=c"), UnsignedInteger { bytes: 1 }),
830            (ffi::c_str!("=B"), UnsignedInteger { bytes: 1 }),
831            (ffi::c_str!("=?"), Bool),
832            (ffi::c_str!("=h"), SignedInteger { bytes: 2 }),
833            (ffi::c_str!("=H"), UnsignedInteger { bytes: 2 }),
834            (ffi::c_str!("=l"), SignedInteger { bytes: 4 }),
835            (ffi::c_str!("=l"), SignedInteger { bytes: 4 }),
836            (ffi::c_str!("=I"), UnsignedInteger { bytes: 4 }),
837            (ffi::c_str!("=L"), UnsignedInteger { bytes: 4 }),
838            (ffi::c_str!("=q"), SignedInteger { bytes: 8 }),
839            (ffi::c_str!("=Q"), UnsignedInteger { bytes: 8 }),
840            (ffi::c_str!("=e"), Float { bytes: 2 }),
841            (ffi::c_str!("=f"), Float { bytes: 4 }),
842            (ffi::c_str!("=d"), Float { bytes: 8 }),
843            (ffi::c_str!("=z"), Unknown),
844            (ffi::c_str!("=0"), Unknown),
845            // unknown prefix -> Unknown
846            (ffi::c_str!(":b"), Unknown),
847        ] {
848            assert_eq!(
849                ElementType::from_format(cstr),
850                expected,
851                "element from format &Cstr: {cstr:?}",
852            );
853        }
854    }
855
856    #[test]
857    fn test_compatible_size() {
858        // for the cast in PyBuffer::shape()
859        assert_eq!(
860            std::mem::size_of::<ffi::Py_ssize_t>(),
861            std::mem::size_of::<usize>()
862        );
863    }
864
865    #[test]
866    fn test_bytes_buffer() {
867        Python::attach(|py| {
868            let bytes = py.eval(ffi::c_str!("b'abcde'"), None, None).unwrap();
869            let buffer = PyBuffer::get(&bytes).unwrap();
870            assert_eq!(buffer.dimensions(), 1);
871            assert_eq!(buffer.item_count(), 5);
872            assert_eq!(buffer.format().to_str().unwrap(), "B");
873            assert_eq!(buffer.shape(), [5]);
874            // single-dimensional buffer is always contiguous
875            assert!(buffer.is_c_contiguous());
876            assert!(buffer.is_fortran_contiguous());
877
878            let slice = buffer.as_slice(py).unwrap();
879            assert_eq!(slice.len(), 5);
880            assert_eq!(slice[0].get(), b'a');
881            assert_eq!(slice[2].get(), b'c');
882
883            assert_eq!(unsafe { *(buffer.get_ptr(&[1]) as *mut u8) }, b'b');
884
885            assert!(buffer.as_mut_slice(py).is_none());
886
887            assert!(buffer.copy_to_slice(py, &mut [0u8]).is_err());
888            let mut arr = [0; 5];
889            buffer.copy_to_slice(py, &mut arr).unwrap();
890            assert_eq!(arr, b"abcde" as &[u8]);
891
892            assert!(buffer.copy_from_slice(py, &[0u8; 5]).is_err());
893            assert_eq!(buffer.to_vec(py).unwrap(), b"abcde");
894        });
895    }
896
897    #[test]
898    fn test_array_buffer() {
899        Python::attach(|py| {
900            let array = py
901                .import("array")
902                .unwrap()
903                .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None)
904                .unwrap();
905            let buffer = PyBuffer::get(&array).unwrap();
906            assert_eq!(buffer.dimensions(), 1);
907            assert_eq!(buffer.item_count(), 4);
908            assert_eq!(buffer.format().to_str().unwrap(), "f");
909            assert_eq!(buffer.shape(), [4]);
910
911            // array creates a 1D contiguious buffer, so it's both C and F contiguous.  This would
912            // be more interesting if we can come up with a 2D buffer but I think it would need a
913            // third-party lib or a custom class.
914
915            // C-contiguous fns
916            let slice = buffer.as_slice(py).unwrap();
917            assert_eq!(slice.len(), 4);
918            assert_eq!(slice[0].get(), 1.0);
919            assert_eq!(slice[3].get(), 2.5);
920
921            let mut_slice = buffer.as_mut_slice(py).unwrap();
922            assert_eq!(mut_slice.len(), 4);
923            assert_eq!(mut_slice[0].get(), 1.0);
924            mut_slice[3].set(2.75);
925            assert_eq!(slice[3].get(), 2.75);
926
927            buffer
928                .copy_from_slice(py, &[10.0f32, 11.0, 12.0, 13.0])
929                .unwrap();
930            assert_eq!(slice[2].get(), 12.0);
931
932            assert_eq!(buffer.to_vec(py).unwrap(), [10.0, 11.0, 12.0, 13.0]);
933
934            // F-contiguous fns
935            let buffer = PyBuffer::get(&array).unwrap();
936            let slice = buffer.as_fortran_slice(py).unwrap();
937            assert_eq!(slice.len(), 4);
938            assert_eq!(slice[1].get(), 11.0);
939
940            let mut_slice = buffer.as_fortran_mut_slice(py).unwrap();
941            assert_eq!(mut_slice.len(), 4);
942            assert_eq!(mut_slice[2].get(), 12.0);
943            mut_slice[3].set(2.75);
944            assert_eq!(slice[3].get(), 2.75);
945
946            buffer
947                .copy_from_fortran_slice(py, &[10.0f32, 11.0, 12.0, 13.0])
948                .unwrap();
949            assert_eq!(slice[2].get(), 12.0);
950
951            assert_eq!(buffer.to_fortran_vec(py).unwrap(), [10.0, 11.0, 12.0, 13.0]);
952        });
953    }
954}