pyo3/types/
bytes.rs

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