Skip to main content

pyo3/types/
bytes.rs

1//! See [`PyBytes`] for more info. This mod also contains its helper type [`PyBytesWriter`].
2
3use crate::ffi_ptr_ext::FfiPtrExt;
4use crate::instance::{Borrowed, Bound};
5#[allow(unused_imports, reason = "used to build docs")]
6use crate::platform::prelude::*;
7use crate::{ffi, Py, PyAny, PyResult, Python};
8#[cfg(RustPython)]
9use crate::{
10    sync::PyOnceLock,
11    types::{PyType, PyTypeMethods},
12};
13use core::ops::Index;
14use core::slice::SliceIndex;
15use core::str;
16
17pub use self::writer::PyBytesWriter;
18
19mod writer;
20
21/// Represents a Python `bytes` object.
22///
23/// This type is immutable.
24///
25/// Values of this type are accessed via PyO3's smart pointers, e.g. as
26/// [`Py<PyBytes>`][crate::Py] or [`Bound<'py, PyBytes>`][Bound].
27///
28/// For APIs available on `bytes` objects, see the [`PyBytesMethods`] trait which is implemented for
29/// [`Bound<'py, PyBytes>`][Bound].
30///
31/// # Equality
32///
33/// For convenience, [`Bound<'py, PyBytes>`][Bound] implements [`PartialEq<[u8]>`][PartialEq] to allow comparing the
34/// data in the Python bytes to a Rust `[u8]` byte slice.
35///
36/// This is not always the most appropriate way to compare Python bytes, as Python bytes subclasses
37/// may have different equality semantics. In situations where subclasses overriding equality might
38/// be relevant, use [`PyAnyMethods::eq`](crate::types::any::PyAnyMethods::eq), at cost of the
39/// additional overhead of a Python method call.
40///
41/// ```rust
42/// # use pyo3::prelude::*;
43/// use pyo3::types::PyBytes;
44///
45/// # Python::attach(|py| {
46/// let py_bytes = PyBytes::new(py, b"foo".as_slice());
47/// // via PartialEq<[u8]>
48/// assert_eq!(py_bytes, b"foo".as_slice());
49///
50/// // via Python equality
51/// let other = PyBytes::new(py, b"foo".as_slice());
52/// assert!(py_bytes.as_any().eq(other).unwrap());
53///
54/// // Note that `eq` will convert its argument to Python using `IntoPyObject`.
55/// // Byte collections are specialized, so that the following slice will indeed
56/// // convert into a `bytes` object and not a `list`:
57/// assert!(py_bytes.as_any().eq(b"foo".as_slice()).unwrap());
58/// # });
59/// ```
60#[repr(transparent)]
61pub struct PyBytes(PyAny);
62
63#[cfg(not(RustPython))]
64pyobject_native_type_core!(PyBytes, pyobject_native_static_type_object!(ffi::PyBytes_Type), "builtins", "bytes", #checkfunction=ffi::PyBytes_Check);
65
66#[cfg(RustPython)]
67pyobject_native_type_core!(
68    PyBytes,
69    |py| {
70        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
71        TYPE.import(py, "builtins", "bytes").unwrap().as_type_ptr()
72    },
73    "builtins",
74    "bytes",
75    #checkfunction=ffi::PyBytes_Check
76);
77
78impl PyBytes {
79    /// Creates a new Python bytestring object.
80    /// The bytestring is initialized by copying the data from the `&[u8]`.
81    ///
82    /// Panics if out of memory.
83    pub fn new<'p>(py: Python<'p>, s: &[u8]) -> Bound<'p, PyBytes> {
84        let ptr = s.as_ptr().cast();
85        let len = s.len() as ffi::Py_ssize_t;
86        unsafe {
87            ffi::PyBytes_FromStringAndSize(ptr, len)
88                .assume_owned(py)
89                .cast_into_unchecked()
90        }
91    }
92
93    /// Creates a new Python `bytes` object with an `init` closure to write its contents.
94    /// Before calling `init` the bytes' contents are zero-initialised.
95    /// * If Python raises a MemoryError on the allocation, `new_with` will return
96    ///   it inside `Err`.
97    /// * If `init` returns `Err(e)`, `new_with` will return `Err(e)`.
98    /// * If `init` returns `Ok(())`, `new_with` will return `Ok(&PyBytes)`.
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// use pyo3::{prelude::*, types::PyBytes};
104    ///
105    /// # fn main() -> PyResult<()> {
106    /// Python::attach(|py| -> PyResult<()> {
107    ///     let py_bytes = PyBytes::new_with(py, 10, |bytes: &mut [u8]| {
108    ///         bytes.copy_from_slice(b"Hello Rust");
109    ///         Ok(())
110    ///     })?;
111    ///     let bytes: &[u8] = py_bytes.extract()?;
112    ///     assert_eq!(bytes, b"Hello Rust");
113    ///     Ok(())
114    /// })
115    /// # }
116    /// ```
117    #[inline]
118    pub fn new_with<F>(py: Python<'_>, len: usize, init: F) -> PyResult<Bound<'_, PyBytes>>
119    where
120        F: FnOnce(&mut [u8]) -> PyResult<()>,
121    {
122        unsafe {
123            let pyptr = ffi::PyBytes_FromStringAndSize(core::ptr::null(), len as ffi::Py_ssize_t);
124            // Check for an allocation error and return it
125            let pybytes = pyptr.assume_owned_or_err(py)?.cast_into_unchecked();
126            let buffer: *mut u8 = ffi::PyBytes_AsString(pyptr).cast();
127            debug_assert!(!buffer.is_null());
128            // Zero-initialise the uninitialised bytestring
129            core::ptr::write_bytes(buffer, 0u8, len);
130            // (Further) Initialise the bytestring in init
131            // If init returns an Err, pypybytearray will automatically deallocate the buffer
132            init(core::slice::from_raw_parts_mut(buffer, len)).map(|_| pybytes)
133        }
134    }
135
136    /// Creates a new Python `bytes` object using a writer closure.
137    ///
138    /// This function allocates a Python `bytes` object with at least `reserved_capacity` bytes of capacity,
139    /// then provides a mutable writer to the closure `write`. The closure can write any number of bytes,
140    /// even more than the reserved capacity; the buffer will grow dynamically as needed.
141    ///
142    /// If `reserved_capacity` is 0, the buffer will start empty and grow as the writer writes data.
143    ///
144    /// After the closure returns, the resulting bytes object contains the written data.
145    ///
146    /// # Example
147    ///
148    /// ```
149    /// use pyo3::{prelude::*, types::PyBytes};
150    ///
151    /// # fn main() -> PyResult<()> {
152    /// Python::attach(|py| -> PyResult<()> {
153    ///     let py_bytes = PyBytes::new_with_writer(py, 0, |writer| {
154    ///         writer.write_bytes(b"hello world")?;
155    ///         Ok(())
156    ///     })?;
157    ///     assert_eq!(py_bytes.as_bytes(), b"hello world");
158    ///     Ok(())
159    /// })
160    /// # }
161    /// ```
162    #[inline]
163    pub fn new_with_writer<'py, F>(
164        py: Python<'py>,
165        reserved_capacity: usize,
166        write: F,
167    ) -> PyResult<Bound<'py, PyBytes>>
168    where
169        F: FnOnce(&mut PyBytesWriter<'py>) -> PyResult<()>,
170    {
171        let mut writer = PyBytesWriter::with_capacity(py, reserved_capacity)?;
172        write(&mut writer)?;
173        writer.try_into()
174    }
175
176    /// Creates a new Python byte string object from a raw pointer and length.
177    ///
178    /// Panics if out of memory.
179    ///
180    /// # Safety
181    ///
182    /// This function dereferences the raw pointer `ptr` as the
183    /// leading pointer of a slice of length `len`. [As with
184    /// `core::slice::from_raw_parts`, this is
185    /// unsafe](https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety).
186    pub unsafe fn from_ptr(py: Python<'_>, ptr: *const u8, len: usize) -> Bound<'_, PyBytes> {
187        unsafe {
188            ffi::PyBytes_FromStringAndSize(ptr.cast(), len as isize)
189                .assume_owned(py)
190                .cast_into_unchecked()
191        }
192    }
193}
194
195/// Implementation of functionality for [`PyBytes`].
196///
197/// These methods are defined for the `Bound<'py, PyBytes>` smart pointer, so to use method call
198/// syntax these methods are separated into a trait, because stable Rust does not yet support
199/// `arbitrary_self_types`.
200#[doc(alias = "PyBytes")]
201pub trait PyBytesMethods<'py>: crate::sealed::Sealed {
202    /// Gets the Python string as a byte slice.
203    fn as_bytes(&self) -> &[u8];
204}
205
206impl<'py> PyBytesMethods<'py> for Bound<'py, PyBytes> {
207    #[inline]
208    fn as_bytes(&self) -> &[u8] {
209        self.as_borrowed().as_bytes()
210    }
211}
212
213impl<'a> Borrowed<'a, '_, PyBytes> {
214    /// Gets the Python string as a byte slice.
215    #[allow(clippy::wrong_self_convention)]
216    pub(crate) fn as_bytes(self) -> &'a [u8] {
217        #[cfg(not(Py_LIMITED_API))]
218        unsafe {
219            let buffer = ffi::PyBytes_AS_STRING(self.as_ptr()).cast::<u8>();
220            let length = ffi::Py_SIZE(self.as_ptr()) as usize;
221            debug_assert!(!buffer.is_null());
222            core::slice::from_raw_parts(buffer, length)
223        }
224
225        #[cfg(Py_LIMITED_API)]
226        unsafe {
227            let buffer = ffi::PyBytes_AsString(self.as_ptr()) as *const u8;
228            let length = ffi::PyBytes_Size(self.as_ptr()) as usize;
229            debug_assert!(!buffer.is_null());
230            core::slice::from_raw_parts(buffer, length)
231        }
232    }
233}
234
235impl Py<PyBytes> {
236    /// Gets the Python bytes as a byte slice. Because Python bytes are
237    /// immutable, the result may be used for as long as the reference to
238    /// `self` is held, including when the GIL is released.
239    pub fn as_bytes<'a>(&'a self, py: Python<'_>) -> &'a [u8] {
240        self.bind_borrowed(py).as_bytes()
241    }
242}
243
244/// This is the same way [Vec] is indexed.
245impl<I: SliceIndex<[u8]>> Index<I> for Bound<'_, PyBytes> {
246    type Output = I::Output;
247
248    fn index(&self, index: I) -> &Self::Output {
249        &self.as_bytes()[index]
250    }
251}
252
253/// Compares whether the Python bytes object is equal to the [u8].
254///
255/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
256impl PartialEq<[u8]> for Bound<'_, PyBytes> {
257    #[inline]
258    fn eq(&self, other: &[u8]) -> bool {
259        self.as_borrowed() == *other
260    }
261}
262
263/// Compares whether the Python bytes object is equal to the [u8].
264///
265/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
266impl PartialEq<&'_ [u8]> for Bound<'_, PyBytes> {
267    #[inline]
268    fn eq(&self, other: &&[u8]) -> bool {
269        self.as_borrowed() == **other
270    }
271}
272
273/// Compares whether the Python bytes object is equal to the [u8].
274///
275/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
276impl PartialEq<Bound<'_, PyBytes>> for [u8] {
277    #[inline]
278    fn eq(&self, other: &Bound<'_, PyBytes>) -> bool {
279        *self == other.as_borrowed()
280    }
281}
282
283/// Compares whether the Python bytes object is equal to the [u8].
284///
285/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
286impl PartialEq<&'_ Bound<'_, PyBytes>> for [u8] {
287    #[inline]
288    fn eq(&self, other: &&Bound<'_, PyBytes>) -> bool {
289        *self == other.as_borrowed()
290    }
291}
292
293/// Compares whether the Python bytes object is equal to the [u8].
294///
295/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
296impl PartialEq<Bound<'_, PyBytes>> for &'_ [u8] {
297    #[inline]
298    fn eq(&self, other: &Bound<'_, PyBytes>) -> bool {
299        **self == other.as_borrowed()
300    }
301}
302
303/// Compares whether the Python bytes object is equal to the [u8].
304///
305/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
306impl PartialEq<[u8]> for &'_ Bound<'_, PyBytes> {
307    #[inline]
308    fn eq(&self, other: &[u8]) -> bool {
309        self.as_borrowed() == other
310    }
311}
312
313/// Compares whether the Python bytes object is equal to the [u8].
314///
315/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
316impl PartialEq<[u8]> for Borrowed<'_, '_, PyBytes> {
317    #[inline]
318    fn eq(&self, other: &[u8]) -> bool {
319        self.as_bytes() == other
320    }
321}
322
323/// Compares whether the Python bytes object is equal to the [u8].
324///
325/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
326impl PartialEq<&[u8]> for Borrowed<'_, '_, PyBytes> {
327    #[inline]
328    fn eq(&self, other: &&[u8]) -> bool {
329        *self == **other
330    }
331}
332
333/// Compares whether the Python bytes object is equal to the [u8].
334///
335/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
336impl PartialEq<Borrowed<'_, '_, PyBytes>> for [u8] {
337    #[inline]
338    fn eq(&self, other: &Borrowed<'_, '_, PyBytes>) -> bool {
339        other == self
340    }
341}
342
343/// Compares whether the Python bytes object is equal to the [u8].
344///
345/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
346impl PartialEq<Borrowed<'_, '_, PyBytes>> for &'_ [u8] {
347    #[inline]
348    fn eq(&self, other: &Borrowed<'_, '_, PyBytes>) -> bool {
349        other == self
350    }
351}
352
353impl<'a> AsRef<[u8]> for Borrowed<'a, '_, PyBytes> {
354    #[inline]
355    fn as_ref(&self) -> &'a [u8] {
356        self.as_bytes()
357    }
358}
359
360impl AsRef<[u8]> for Bound<'_, PyBytes> {
361    #[inline]
362    fn as_ref(&self) -> &[u8] {
363        self.as_bytes()
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370    use crate::types::PyAnyMethods as _;
371
372    #[test]
373    fn test_bytes_index() {
374        Python::attach(|py| {
375            let bytes = PyBytes::new(py, b"Hello World");
376            assert_eq!(bytes[1], b'e');
377        });
378    }
379
380    #[test]
381    fn test_bound_bytes_index() {
382        Python::attach(|py| {
383            let bytes = PyBytes::new(py, b"Hello World");
384            assert_eq!(bytes[1], b'e');
385
386            let bytes = &bytes;
387            assert_eq!(bytes[1], b'e');
388        });
389    }
390
391    #[test]
392    fn test_bytes_new_with() -> super::PyResult<()> {
393        Python::attach(|py| -> super::PyResult<()> {
394            let py_bytes = PyBytes::new_with(py, 10, |b: &mut [u8]| {
395                b.copy_from_slice(b"Hello Rust");
396                Ok(())
397            })?;
398            let bytes: &[u8] = py_bytes.extract()?;
399            assert_eq!(bytes, b"Hello Rust");
400            Ok(())
401        })
402    }
403
404    #[test]
405    fn test_bytes_new_with_zero_initialised() -> super::PyResult<()> {
406        Python::attach(|py| -> super::PyResult<()> {
407            let py_bytes = PyBytes::new_with(py, 10, |_b: &mut [u8]| Ok(()))?;
408            let bytes: &[u8] = py_bytes.extract()?;
409            assert_eq!(bytes, &[0; 10]);
410            Ok(())
411        })
412    }
413
414    #[test]
415    fn test_bytes_new_with_error() {
416        use crate::exceptions::PyValueError;
417        Python::attach(|py| {
418            let py_bytes_result = PyBytes::new_with(py, 10, |_b: &mut [u8]| {
419                Err(PyValueError::new_err("Hello Crustaceans!"))
420            });
421            assert!(py_bytes_result.is_err());
422            assert!(py_bytes_result
423                .err()
424                .unwrap()
425                .is_instance_of::<PyValueError>(py));
426        });
427    }
428
429    #[test]
430    fn test_comparisons() {
431        Python::attach(|py| {
432            let b = b"hello, world".as_slice();
433            let py_bytes = PyBytes::new(py, b);
434
435            assert_eq!(py_bytes, b"hello, world".as_slice());
436
437            assert_eq!(py_bytes, b);
438            assert_eq!(&py_bytes, b);
439            assert_eq!(b, py_bytes);
440            assert_eq!(b, &py_bytes);
441
442            assert_eq!(py_bytes, *b);
443            assert_eq!(&py_bytes, *b);
444            assert_eq!(*b, py_bytes);
445            assert_eq!(*b, &py_bytes);
446
447            let py_string = py_bytes.as_borrowed();
448
449            assert_eq!(py_string, b);
450            assert_eq!(&py_string, b);
451            assert_eq!(b, py_string);
452            assert_eq!(b, &py_string);
453
454            assert_eq!(py_string, *b);
455            assert_eq!(*b, py_string);
456        })
457    }
458
459    #[test]
460    #[cfg(not(Py_LIMITED_API))]
461    fn test_as_string() {
462        Python::attach(|py| {
463            let b = b"hello, world".as_slice();
464            let py_bytes = PyBytes::new(py, b);
465            unsafe {
466                assert_eq!(
467                    ffi::PyBytes_AsString(py_bytes.as_ptr()) as *const core::ffi::c_char,
468                    ffi::PyBytes_AS_STRING(py_bytes.as_ptr()) as *const core::ffi::c_char
469                );
470            }
471        })
472    }
473
474    #[test]
475    fn test_as_ref_slice() {
476        Python::attach(|py| {
477            let b = b"hello, world";
478            let py_bytes = PyBytes::new(py, b);
479            let ref_bound: &[u8] = py_bytes.as_ref();
480            assert_eq!(ref_bound, b);
481            let py_bytes_borrowed = py_bytes.as_borrowed();
482            let ref_borrowed: &[u8] = py_bytes_borrowed.as_ref();
483            assert_eq!(ref_borrowed, b);
484        })
485    }
486
487    #[test]
488    fn test_py_as_bytes() {
489        let pyobj: Py<PyBytes> = Python::attach(|py| PyBytes::new(py, b"abc").unbind());
490
491        let data = Python::attach(|py| pyobj.as_bytes(py));
492
493        assert_eq!(data, b"abc");
494
495        Python::attach(move |_py| drop(pyobj));
496    }
497
498    #[test]
499    fn test_with_writer() {
500        Python::attach(|py| {
501            let bytes = PyBytes::new_with_writer(py, 0, |writer| {
502                writer.write_bytes(b"hallo")?;
503                Ok(())
504            })
505            .unwrap();
506
507            assert_eq!(bytes.as_bytes(), b"hallo");
508        })
509    }
510}