pyo3/types/
bytearray.rs

1use crate::err::{PyErr, PyResult};
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::instance::{Borrowed, Bound};
4use crate::py_result_ext::PyResultExt;
5use crate::types::any::PyAnyMethods;
6use crate::{ffi, PyAny, Python};
7use std::slice;
8
9/// Represents a Python `bytearray`.
10///
11/// Values of this type are accessed via PyO3's smart pointers, e.g. as
12/// [`Py<PyByteArray>`][crate::Py] or [`Bound<'py, PyByteArray>`][Bound].
13///
14/// For APIs available on `bytearray` objects, see the [`PyByteArrayMethods`] trait which is implemented for
15/// [`Bound<'py, PyByteArray>`][Bound].
16#[repr(transparent)]
17pub struct PyByteArray(PyAny);
18
19pyobject_native_type_core!(PyByteArray, pyobject_native_static_type_object!(ffi::PyByteArray_Type), #checkfunction=ffi::PyByteArray_Check);
20
21impl PyByteArray {
22    /// Creates a new Python bytearray object.
23    ///
24    /// The byte string is initialized by copying the data from the `&[u8]`.
25    pub fn new<'py>(py: Python<'py>, src: &[u8]) -> Bound<'py, PyByteArray> {
26        let ptr = src.as_ptr().cast();
27        let len = src.len() as ffi::Py_ssize_t;
28        unsafe {
29            ffi::PyByteArray_FromStringAndSize(ptr, len)
30                .assume_owned(py)
31                .downcast_into_unchecked()
32        }
33    }
34
35    /// Deprecated name for [`PyByteArray::new`].
36    #[deprecated(since = "0.23.0", note = "renamed to `PyByteArray::new`")]
37    #[inline]
38    pub fn new_bound<'py>(py: Python<'py>, src: &[u8]) -> Bound<'py, PyByteArray> {
39        Self::new(py, src)
40    }
41
42    /// Creates a new Python `bytearray` object with an `init` closure to write its contents.
43    /// Before calling `init` the bytearray is zero-initialised.
44    /// * If Python raises a MemoryError on the allocation, `new_with` will return
45    ///   it inside `Err`.
46    /// * If `init` returns `Err(e)`, `new_with` will return `Err(e)`.
47    /// * If `init` returns `Ok(())`, `new_with` will return `Ok(&PyByteArray)`.
48    ///
49    /// # Examples
50    ///
51    /// ```
52    /// use pyo3::{prelude::*, types::PyByteArray};
53    ///
54    /// # fn main() -> PyResult<()> {
55    /// Python::with_gil(|py| -> PyResult<()> {
56    ///     let py_bytearray = PyByteArray::new_with(py, 10, |bytes: &mut [u8]| {
57    ///         bytes.copy_from_slice(b"Hello Rust");
58    ///         Ok(())
59    ///     })?;
60    ///     let bytearray: &[u8] = unsafe { py_bytearray.as_bytes() };
61    ///     assert_eq!(bytearray, b"Hello Rust");
62    ///     Ok(())
63    /// })
64    /// # }
65    /// ```
66    pub fn new_with<F>(py: Python<'_>, len: usize, init: F) -> PyResult<Bound<'_, PyByteArray>>
67    where
68        F: FnOnce(&mut [u8]) -> PyResult<()>,
69    {
70        unsafe {
71            // Allocate buffer and check for an error
72            let pybytearray: Bound<'_, Self> =
73                ffi::PyByteArray_FromStringAndSize(std::ptr::null(), len as ffi::Py_ssize_t)
74                    .assume_owned_or_err(py)?
75                    .downcast_into_unchecked();
76
77            let buffer: *mut u8 = ffi::PyByteArray_AsString(pybytearray.as_ptr()).cast();
78            debug_assert!(!buffer.is_null());
79            // Zero-initialise the uninitialised bytearray
80            std::ptr::write_bytes(buffer, 0u8, len);
81            // (Further) Initialise the bytearray in init
82            // If init returns an Err, pypybytearray will automatically deallocate the buffer
83            init(std::slice::from_raw_parts_mut(buffer, len)).map(|_| pybytearray)
84        }
85    }
86
87    /// Deprecated name for [`PyByteArray::new_with`].
88    #[deprecated(since = "0.23.0", note = "renamed to `PyByteArray::new_with`")]
89    #[inline]
90    pub fn new_bound_with<F>(
91        py: Python<'_>,
92        len: usize,
93        init: F,
94    ) -> PyResult<Bound<'_, PyByteArray>>
95    where
96        F: FnOnce(&mut [u8]) -> PyResult<()>,
97    {
98        Self::new_with(py, len, init)
99    }
100
101    /// Creates a new Python `bytearray` object from another Python object that
102    /// implements the buffer protocol.
103    pub fn from<'py>(src: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyByteArray>> {
104        unsafe {
105            ffi::PyByteArray_FromObject(src.as_ptr())
106                .assume_owned_or_err(src.py())
107                .downcast_into_unchecked()
108        }
109    }
110
111    ///Deprecated name for [`PyByteArray::from`].
112    #[deprecated(since = "0.23.0", note = "renamed to `PyByteArray::from`")]
113    #[inline]
114    pub fn from_bound<'py>(src: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyByteArray>> {
115        Self::from(src)
116    }
117}
118
119/// Implementation of functionality for [`PyByteArray`].
120///
121/// These methods are defined for the `Bound<'py, PyByteArray>` smart pointer, so to use method call
122/// syntax these methods are separated into a trait, because stable Rust does not yet support
123/// `arbitrary_self_types`.
124#[doc(alias = "PyByteArray")]
125pub trait PyByteArrayMethods<'py>: crate::sealed::Sealed {
126    /// Gets the length of the bytearray.
127    fn len(&self) -> usize;
128
129    /// Checks if the bytearray is empty.
130    fn is_empty(&self) -> bool;
131
132    /// Gets the start of the buffer containing the contents of the bytearray.
133    ///
134    /// # Safety
135    ///
136    /// See the safety requirements of [`PyByteArrayMethods::as_bytes`] and [`PyByteArrayMethods::as_bytes_mut`].
137    fn data(&self) -> *mut u8;
138
139    /// Extracts a slice of the `ByteArray`'s entire buffer.
140    ///
141    /// # Safety
142    ///
143    /// Mutation of the `bytearray` invalidates the slice. If it is used afterwards, the behavior is
144    /// undefined.
145    ///
146    /// These mutations may occur in Python code as well as from Rust:
147    /// - Calling methods like [`PyByteArrayMethods::as_bytes_mut`] and [`PyByteArrayMethods::resize`] will
148    ///   invalidate the slice.
149    /// - Actions like dropping objects or raising exceptions can invoke `__del__`methods or signal
150    ///   handlers, which may execute arbitrary Python code. This means that if Python code has a
151    ///   reference to the `bytearray` you cannot safely use the vast majority of PyO3's API whilst
152    ///   using the slice.
153    ///
154    /// As a result, this slice should only be used for short-lived operations without executing any
155    /// Python code, such as copying into a Vec.
156    ///
157    /// # Examples
158    ///
159    /// ```rust
160    /// use pyo3::prelude::*;
161    /// use pyo3::exceptions::PyRuntimeError;
162    /// use pyo3::types::PyByteArray;
163    ///
164    /// #[pyfunction]
165    /// fn a_valid_function(bytes: &Bound<'_, PyByteArray>) -> PyResult<()> {
166    ///     let section = {
167    ///         // SAFETY: We promise to not let the interpreter regain control
168    ///         // or invoke any PyO3 APIs while using the slice.
169    ///         let slice = unsafe { bytes.as_bytes() };
170    ///
171    ///         // Copy only a section of `bytes` while avoiding
172    ///         // `to_vec` which copies the entire thing.
173    ///         let section = slice
174    ///             .get(6..11)
175    ///             .ok_or_else(|| PyRuntimeError::new_err("input is not long enough"))?;
176    ///         Vec::from(section)
177    ///     };
178    ///
179    ///     // Now we can do things with `section` and call PyO3 APIs again.
180    ///     // ...
181    ///     # assert_eq!(&section, b"world");
182    ///
183    ///     Ok(())
184    /// }
185    /// # fn main() -> PyResult<()> {
186    /// #     Python::with_gil(|py| -> PyResult<()> {
187    /// #         let fun = wrap_pyfunction!(a_valid_function, py)?;
188    /// #         let locals = pyo3::types::PyDict::new(py);
189    /// #         locals.set_item("a_valid_function", fun)?;
190    /// #
191    /// #         py.run(pyo3::ffi::c_str!(
192    /// # r#"b = bytearray(b"hello world")
193    /// # a_valid_function(b)
194    /// #
195    /// # try:
196    /// #     a_valid_function(bytearray())
197    /// # except RuntimeError as e:
198    /// #     assert str(e) == 'input is not long enough'"#),
199    /// #             None,
200    /// #             Some(&locals),
201    /// #         )?;
202    /// #
203    /// #         Ok(())
204    /// #     })
205    /// # }
206    /// ```
207    ///
208    /// # Incorrect usage
209    ///
210    /// The following `bug` function is unsound ⚠️
211    ///
212    /// ```rust,no_run
213    /// # use pyo3::prelude::*;
214    /// # use pyo3::types::PyByteArray;
215    ///
216    /// # #[allow(dead_code)]
217    /// #[pyfunction]
218    /// fn bug(py: Python<'_>, bytes: &Bound<'_, PyByteArray>) {
219    ///     let slice = unsafe { bytes.as_bytes() };
220    ///
221    ///     // This explicitly yields control back to the Python interpreter...
222    ///     // ...but it's not always this obvious. Many things do this implicitly.
223    ///     py.allow_threads(|| {
224    ///         // Python code could be mutating through its handle to `bytes`,
225    ///         // which makes reading it a data race, which is undefined behavior.
226    ///         println!("{:?}", slice[0]);
227    ///     });
228    ///
229    ///     // Python code might have mutated it, so we can not rely on the slice
230    ///     // remaining valid. As such this is also undefined behavior.
231    ///     println!("{:?}", slice[0]);
232    /// }
233    /// ```
234    unsafe fn as_bytes(&self) -> &[u8];
235
236    /// Extracts a mutable slice of the `ByteArray`'s entire buffer.
237    ///
238    /// # Safety
239    ///
240    /// Any other accesses of the `bytearray`'s buffer invalidate the slice. If it is used
241    /// afterwards, the behavior is undefined. The safety requirements of [`PyByteArrayMethods::as_bytes`]
242    /// apply to this function as well.
243    #[allow(clippy::mut_from_ref)]
244    unsafe fn as_bytes_mut(&self) -> &mut [u8];
245
246    /// Copies the contents of the bytearray to a Rust vector.
247    ///
248    /// # Examples
249    ///
250    /// ```
251    /// # use pyo3::prelude::*;
252    /// # use pyo3::types::PyByteArray;
253    /// # Python::with_gil(|py| {
254    /// let bytearray = PyByteArray::new(py, b"Hello World.");
255    /// let mut copied_message = bytearray.to_vec();
256    /// assert_eq!(b"Hello World.", copied_message.as_slice());
257    ///
258    /// copied_message[11] = b'!';
259    /// assert_eq!(b"Hello World!", copied_message.as_slice());
260    ///
261    /// pyo3::py_run!(py, bytearray, "assert bytearray == b'Hello World.'");
262    /// # });
263    /// ```
264    fn to_vec(&self) -> Vec<u8>;
265
266    /// Resizes the bytearray object to the new length `len`.
267    ///
268    /// Note that this will invalidate any pointers obtained by [PyByteArrayMethods::data], as well as
269    /// any (unsafe) slices obtained from [PyByteArrayMethods::as_bytes] and [PyByteArrayMethods::as_bytes_mut].
270    fn resize(&self, len: usize) -> PyResult<()>;
271}
272
273impl<'py> PyByteArrayMethods<'py> for Bound<'py, PyByteArray> {
274    #[inline]
275    fn len(&self) -> usize {
276        // non-negative Py_ssize_t should always fit into Rust usize
277        unsafe { ffi::PyByteArray_Size(self.as_ptr()) as usize }
278    }
279
280    fn is_empty(&self) -> bool {
281        self.len() == 0
282    }
283
284    fn data(&self) -> *mut u8 {
285        self.as_borrowed().data()
286    }
287
288    unsafe fn as_bytes(&self) -> &[u8] {
289        self.as_borrowed().as_bytes()
290    }
291
292    #[allow(clippy::mut_from_ref)]
293    unsafe fn as_bytes_mut(&self) -> &mut [u8] {
294        self.as_borrowed().as_bytes_mut()
295    }
296
297    fn to_vec(&self) -> Vec<u8> {
298        unsafe { self.as_bytes() }.to_vec()
299    }
300
301    fn resize(&self, len: usize) -> PyResult<()> {
302        unsafe {
303            let result = ffi::PyByteArray_Resize(self.as_ptr(), len as ffi::Py_ssize_t);
304            if result == 0 {
305                Ok(())
306            } else {
307                Err(PyErr::fetch(self.py()))
308            }
309        }
310    }
311}
312
313impl<'a> Borrowed<'a, '_, PyByteArray> {
314    fn data(&self) -> *mut u8 {
315        unsafe { ffi::PyByteArray_AsString(self.as_ptr()).cast() }
316    }
317
318    #[allow(clippy::wrong_self_convention)]
319    unsafe fn as_bytes(self) -> &'a [u8] {
320        slice::from_raw_parts(self.data(), self.len())
321    }
322
323    #[allow(clippy::wrong_self_convention)]
324    unsafe fn as_bytes_mut(self) -> &'a mut [u8] {
325        slice::from_raw_parts_mut(self.data(), self.len())
326    }
327}
328
329impl<'py> TryFrom<&Bound<'py, PyAny>> for Bound<'py, PyByteArray> {
330    type Error = crate::PyErr;
331
332    /// Creates a new Python `bytearray` object from another Python object that
333    /// implements the buffer protocol.
334    fn try_from(value: &Bound<'py, PyAny>) -> Result<Self, Self::Error> {
335        PyByteArray::from(value)
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use crate::types::{PyAnyMethods, PyByteArray, PyByteArrayMethods};
342    use crate::{exceptions, Bound, PyAny, PyObject, Python};
343
344    #[test]
345    fn test_len() {
346        Python::with_gil(|py| {
347            let src = b"Hello Python";
348            let bytearray = PyByteArray::new(py, src);
349            assert_eq!(src.len(), bytearray.len());
350        });
351    }
352
353    #[test]
354    fn test_as_bytes() {
355        Python::with_gil(|py| {
356            let src = b"Hello Python";
357            let bytearray = PyByteArray::new(py, src);
358
359            let slice = unsafe { bytearray.as_bytes() };
360            assert_eq!(src, slice);
361            assert_eq!(bytearray.data() as *const _, slice.as_ptr());
362        });
363    }
364
365    #[test]
366    fn test_as_bytes_mut() {
367        Python::with_gil(|py| {
368            let src = b"Hello Python";
369            let bytearray = PyByteArray::new(py, src);
370
371            let slice = unsafe { bytearray.as_bytes_mut() };
372            assert_eq!(src, slice);
373            assert_eq!(bytearray.data(), slice.as_mut_ptr());
374
375            slice[0..5].copy_from_slice(b"Hi...");
376
377            assert_eq!(bytearray.str().unwrap(), "bytearray(b'Hi... Python')");
378        });
379    }
380
381    #[test]
382    fn test_to_vec() {
383        Python::with_gil(|py| {
384            let src = b"Hello Python";
385            let bytearray = PyByteArray::new(py, src);
386
387            let vec = bytearray.to_vec();
388            assert_eq!(src, vec.as_slice());
389        });
390    }
391
392    #[test]
393    fn test_from() {
394        Python::with_gil(|py| {
395            let src = b"Hello Python";
396            let bytearray = PyByteArray::new(py, src);
397
398            let ba: PyObject = bytearray.into();
399            let bytearray = PyByteArray::from(ba.bind(py)).unwrap();
400
401            assert_eq!(src, unsafe { bytearray.as_bytes() });
402        });
403    }
404
405    #[test]
406    fn test_from_err() {
407        Python::with_gil(|py| {
408            if let Err(err) = PyByteArray::from(py.None().bind(py)) {
409                assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
410            } else {
411                panic!("error");
412            }
413        });
414    }
415
416    #[test]
417    fn test_try_from() {
418        Python::with_gil(|py| {
419            let src = b"Hello Python";
420            let bytearray: &Bound<'_, PyAny> = &PyByteArray::new(py, src);
421            let bytearray: Bound<'_, PyByteArray> = TryInto::try_into(bytearray).unwrap();
422
423            assert_eq!(src, unsafe { bytearray.as_bytes() });
424        });
425    }
426
427    #[test]
428    fn test_resize() {
429        Python::with_gil(|py| {
430            let src = b"Hello Python";
431            let bytearray = PyByteArray::new(py, src);
432
433            bytearray.resize(20).unwrap();
434            assert_eq!(20, bytearray.len());
435        });
436    }
437
438    #[test]
439    fn test_byte_array_new_with() -> super::PyResult<()> {
440        Python::with_gil(|py| -> super::PyResult<()> {
441            let py_bytearray = PyByteArray::new_with(py, 10, |b: &mut [u8]| {
442                b.copy_from_slice(b"Hello Rust");
443                Ok(())
444            })?;
445            let bytearray: &[u8] = unsafe { py_bytearray.as_bytes() };
446            assert_eq!(bytearray, b"Hello Rust");
447            Ok(())
448        })
449    }
450
451    #[test]
452    fn test_byte_array_new_with_zero_initialised() -> super::PyResult<()> {
453        Python::with_gil(|py| -> super::PyResult<()> {
454            let py_bytearray = PyByteArray::new_with(py, 10, |_b: &mut [u8]| Ok(()))?;
455            let bytearray: &[u8] = unsafe { py_bytearray.as_bytes() };
456            assert_eq!(bytearray, &[0; 10]);
457            Ok(())
458        })
459    }
460
461    #[test]
462    fn test_byte_array_new_with_error() {
463        use crate::exceptions::PyValueError;
464        Python::with_gil(|py| {
465            let py_bytearray_result = PyByteArray::new_with(py, 10, |_b: &mut [u8]| {
466                Err(PyValueError::new_err("Hello Crustaceans!"))
467            });
468            assert!(py_bytearray_result.is_err());
469            assert!(py_bytearray_result
470                .err()
471                .unwrap()
472                .is_instance_of::<PyValueError>(py));
473        })
474    }
475}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here