Skip to main content

pyo3/
pybacked.rs

1// TODO https://github.com/PyO3/pyo3/issues/5487
2#![allow(clippy::undocumented_unsafe_blocks)]
3
4//! Contains types for working with Python objects that own the underlying data.
5
6#[cfg(feature = "experimental-inspect")]
7use crate::inspect::PyStaticExpr;
8#[cfg(feature = "experimental-inspect")]
9use crate::type_hint_union;
10use crate::{
11    types::{
12        bytearray::PyByteArrayMethods, bytes::PyBytesMethods, string::PyStringMethods, PyByteArray,
13        PyBytes, PyString, PyTuple,
14    },
15    Borrowed, Bound, CastError, FromPyObject, IntoPyObject, Py, PyAny, PyErr, PyTypeInfo, Python,
16};
17use alloc::sync::Arc;
18use core::{borrow::Borrow, convert::Infallible, ops::Deref, ptr::NonNull};
19
20/// An equivalent to `String` where the storage is owned by a Python `bytes` or `str` object.
21///
22/// On Python 3.10+ or when not using the stable API, this type is guaranteed to contain a Python `str`
23/// for the underlying data.
24///
25/// This type gives access to the underlying data via a `Deref` implementation.
26#[cfg_attr(feature = "py-clone", derive(Clone))]
27pub struct PyBackedStr {
28    #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
29    storage: Py<PyString>,
30    #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
31    storage: Py<PyBytes>,
32    data: NonNull<str>,
33}
34
35impl PyBackedStr {
36    /// Clones this by incrementing the reference count of the underlying Python object.
37    ///
38    /// Similar to [`Py::clone_ref`], this method is always available, even when the `py-clone` feature is disabled.
39    #[inline]
40    pub fn clone_ref(&self, py: Python<'_>) -> Self {
41        Self {
42            storage: self.storage.clone_ref(py),
43            data: self.data,
44        }
45    }
46
47    /// Returns the underlying data as a `&str` slice.
48    #[inline]
49    pub fn as_str(&self) -> &str {
50        // Safety: `data` is known to be immutable and owned by self
51        unsafe { self.data.as_ref() }
52    }
53
54    /// Returns the underlying data as a Python `str`.
55    ///
56    /// Older versions of the Python stable API do not support this zero-cost conversion.
57    #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
58    #[inline]
59    pub fn as_py_str(&self) -> &Py<PyString> {
60        &self.storage
61    }
62}
63
64impl Deref for PyBackedStr {
65    type Target = str;
66    #[inline]
67    fn deref(&self) -> &str {
68        self.as_str()
69    }
70}
71
72impl AsRef<str> for PyBackedStr {
73    #[inline]
74    fn as_ref(&self) -> &str {
75        self
76    }
77}
78
79impl AsRef<[u8]> for PyBackedStr {
80    #[inline]
81    fn as_ref(&self) -> &[u8] {
82        self.as_bytes()
83    }
84}
85
86impl Borrow<str> for PyBackedStr {
87    #[inline]
88    fn borrow(&self) -> &str {
89        self
90    }
91}
92
93// Safety: the underlying Python str (or bytes) is immutable and
94// safe to share between threads
95unsafe impl Send for PyBackedStr {}
96unsafe impl Sync for PyBackedStr {}
97
98impl core::fmt::Display for PyBackedStr {
99    #[inline]
100    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
101        self.deref().fmt(f)
102    }
103}
104
105impl_traits!(PyBackedStr, str);
106
107impl TryFrom<Bound<'_, PyString>> for PyBackedStr {
108    type Error = PyErr;
109    fn try_from(py_string: Bound<'_, PyString>) -> Result<Self, Self::Error> {
110        #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
111        {
112            let s = py_string.to_str()?;
113            let data = NonNull::from(s);
114            Ok(Self {
115                storage: py_string.unbind(),
116                data,
117            })
118        }
119        #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
120        {
121            let bytes = py_string.encode_utf8()?;
122            let s = unsafe { core::str::from_utf8_unchecked(bytes.as_bytes()) };
123            let data = NonNull::from(s);
124            Ok(Self {
125                storage: bytes.unbind(),
126                data,
127            })
128        }
129    }
130}
131
132impl FromPyObject<'_, '_> for PyBackedStr {
133    type Error = PyErr;
134
135    #[cfg(feature = "experimental-inspect")]
136    const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
137
138    #[inline]
139    fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
140        let py_string = obj.cast::<PyString>()?.to_owned();
141        Self::try_from(py_string)
142    }
143}
144
145impl<'py> IntoPyObject<'py> for PyBackedStr {
146    type Target = PyString;
147    type Output = Bound<'py, Self::Target>;
148    type Error = Infallible;
149
150    #[cfg(feature = "experimental-inspect")]
151    const OUTPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
152
153    #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
154    #[inline]
155    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
156        Ok(self.storage.into_bound(py))
157    }
158
159    #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
160    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
161        Ok(PyString::new(py, &self))
162    }
163}
164
165impl<'py> IntoPyObject<'py> for &PyBackedStr {
166    type Target = PyString;
167    type Output = Bound<'py, Self::Target>;
168    type Error = Infallible;
169
170    #[cfg(feature = "experimental-inspect")]
171    const OUTPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
172
173    #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
174    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
175        Ok(self.storage.bind(py).to_owned())
176    }
177
178    #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
179    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
180        Ok(PyString::new(py, self))
181    }
182}
183
184/// A wrapper around `[u8]` where the storage is either owned by a Python `bytes` object, or a Rust `Box<[u8]>`.
185///
186/// This type gives access to the underlying data via a `Deref` implementation.
187#[cfg_attr(feature = "py-clone", derive(Clone))]
188pub struct PyBackedBytes {
189    storage: PyBackedBytesStorage,
190    data: NonNull<[u8]>,
191}
192
193#[cfg_attr(feature = "py-clone", derive(Clone))]
194enum PyBackedBytesStorage {
195    Python(Py<PyBytes>),
196    Rust(Arc<[u8]>),
197}
198
199impl PyBackedBytes {
200    /// Clones this by incrementing the reference count of the underlying data.
201    ///
202    /// Similar to [`Py::clone_ref`], this method is always available, even when the `py-clone` feature is disabled.
203    pub fn clone_ref(&self, py: Python<'_>) -> Self {
204        Self {
205            storage: match &self.storage {
206                PyBackedBytesStorage::Python(bytes) => {
207                    PyBackedBytesStorage::Python(bytes.clone_ref(py))
208                }
209                PyBackedBytesStorage::Rust(bytes) => PyBackedBytesStorage::Rust(bytes.clone()),
210            },
211            data: self.data,
212        }
213    }
214}
215
216impl Deref for PyBackedBytes {
217    type Target = [u8];
218    fn deref(&self) -> &[u8] {
219        // Safety: `data` is known to be immutable and owned by self
220        unsafe { self.data.as_ref() }
221    }
222}
223
224impl AsRef<[u8]> for PyBackedBytes {
225    fn as_ref(&self) -> &[u8] {
226        self
227    }
228}
229
230// Safety: the underlying Python bytes or Rust bytes is immutable and
231// safe to share between threads
232unsafe impl Send for PyBackedBytes {}
233unsafe impl Sync for PyBackedBytes {}
234
235impl<const N: usize> PartialEq<[u8; N]> for PyBackedBytes {
236    fn eq(&self, other: &[u8; N]) -> bool {
237        self.deref() == other
238    }
239}
240
241impl<const N: usize> PartialEq<PyBackedBytes> for [u8; N] {
242    fn eq(&self, other: &PyBackedBytes) -> bool {
243        self == other.deref()
244    }
245}
246
247impl<const N: usize> PartialEq<&[u8; N]> for PyBackedBytes {
248    fn eq(&self, other: &&[u8; N]) -> bool {
249        self.deref() == *other
250    }
251}
252
253impl<const N: usize> PartialEq<PyBackedBytes> for &[u8; N] {
254    fn eq(&self, other: &PyBackedBytes) -> bool {
255        self == &other.deref()
256    }
257}
258
259impl_traits!(PyBackedBytes, [u8]);
260
261impl From<Bound<'_, PyBytes>> for PyBackedBytes {
262    fn from(py_bytes: Bound<'_, PyBytes>) -> Self {
263        let b = py_bytes.as_bytes();
264        let data = NonNull::from(b);
265        Self {
266            storage: PyBackedBytesStorage::Python(py_bytes.to_owned().unbind()),
267            data,
268        }
269    }
270}
271
272impl From<Bound<'_, PyByteArray>> for PyBackedBytes {
273    fn from(py_bytearray: Bound<'_, PyByteArray>) -> Self {
274        let s = Arc::<[u8]>::from(py_bytearray.to_vec());
275        let data = NonNull::from(s.as_ref());
276        Self {
277            storage: PyBackedBytesStorage::Rust(s),
278            data,
279        }
280    }
281}
282
283impl<'a, 'py> FromPyObject<'a, 'py> for PyBackedBytes {
284    type Error = CastError<'a, 'py>;
285
286    #[cfg(feature = "experimental-inspect")]
287    const INPUT_TYPE: PyStaticExpr = type_hint_union!(PyBytes::TYPE_HINT, PyByteArray::TYPE_HINT);
288
289    fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
290        if let Ok(bytes) = obj.cast::<PyBytes>() {
291            Ok(Self::from(bytes.to_owned()))
292        } else if let Ok(bytearray) = obj.cast::<PyByteArray>() {
293            Ok(Self::from(bytearray.to_owned()))
294        } else {
295            Err(CastError::new(
296                obj,
297                PyTuple::new(
298                    obj.py(),
299                    [
300                        PyBytes::type_object(obj.py()),
301                        PyByteArray::type_object(obj.py()),
302                    ],
303                )
304                .unwrap()
305                .into_any(),
306            ))
307        }
308    }
309}
310
311impl<'py> IntoPyObject<'py> for PyBackedBytes {
312    type Target = PyBytes;
313    type Output = Bound<'py, Self::Target>;
314    type Error = Infallible;
315
316    #[cfg(feature = "experimental-inspect")]
317    const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT;
318
319    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
320        match self.storage {
321            PyBackedBytesStorage::Python(bytes) => Ok(bytes.into_bound(py)),
322            PyBackedBytesStorage::Rust(bytes) => Ok(PyBytes::new(py, &bytes)),
323        }
324    }
325}
326
327impl<'py> IntoPyObject<'py> for &PyBackedBytes {
328    type Target = PyBytes;
329    type Output = Bound<'py, Self::Target>;
330    type Error = Infallible;
331
332    #[cfg(feature = "experimental-inspect")]
333    const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT;
334
335    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
336        match &self.storage {
337            PyBackedBytesStorage::Python(bytes) => Ok(bytes.bind(py).clone()),
338            PyBackedBytesStorage::Rust(bytes) => Ok(PyBytes::new(py, bytes)),
339        }
340    }
341}
342
343macro_rules! impl_traits {
344    ($slf:ty, $equiv:ty) => {
345        impl core::fmt::Debug for $slf {
346            #[inline]
347            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
348                self.deref().fmt(f)
349            }
350        }
351
352        impl PartialEq for $slf {
353            #[inline]
354            fn eq(&self, other: &Self) -> bool {
355                self.deref() == other.deref()
356            }
357        }
358
359        impl PartialEq<$equiv> for $slf {
360            #[inline]
361            fn eq(&self, other: &$equiv) -> bool {
362                self.deref() == other
363            }
364        }
365
366        impl PartialEq<&$equiv> for $slf {
367            #[inline]
368            fn eq(&self, other: &&$equiv) -> bool {
369                self.deref() == *other
370            }
371        }
372
373        impl PartialEq<$slf> for $equiv {
374            #[inline]
375            fn eq(&self, other: &$slf) -> bool {
376                self == other.deref()
377            }
378        }
379
380        impl PartialEq<$slf> for &$equiv {
381            #[inline]
382            fn eq(&self, other: &$slf) -> bool {
383                self == &other.deref()
384            }
385        }
386
387        impl Eq for $slf {}
388
389        impl PartialOrd for $slf {
390            #[inline]
391            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
392                Some(self.cmp(other))
393            }
394        }
395
396        impl PartialOrd<$equiv> for $slf {
397            #[inline]
398            fn partial_cmp(&self, other: &$equiv) -> Option<core::cmp::Ordering> {
399                self.deref().partial_cmp(other)
400            }
401        }
402
403        impl PartialOrd<$slf> for $equiv {
404            #[inline]
405            fn partial_cmp(&self, other: &$slf) -> Option<core::cmp::Ordering> {
406                self.partial_cmp(other.deref())
407            }
408        }
409
410        impl Ord for $slf {
411            #[inline]
412            fn cmp(&self, other: &Self) -> core::cmp::Ordering {
413                self.deref().cmp(other.deref())
414            }
415        }
416
417        impl core::hash::Hash for $slf {
418            #[inline]
419            fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
420                self.deref().hash(state)
421            }
422        }
423    };
424}
425use impl_traits;
426
427#[cfg(test)]
428mod test {
429    use super::*;
430    use crate::impl_::pyclass::{value_of, IsSend, IsSync};
431    use crate::types::PyAnyMethods as _;
432    use crate::{IntoPyObject, Python};
433    use core::hash::{Hash, Hasher};
434    use std::collections::hash_map::DefaultHasher;
435
436    #[test]
437    fn py_backed_str_empty() {
438        Python::attach(|py| {
439            let s = PyString::new(py, "");
440            let py_backed_str = s.extract::<PyBackedStr>().unwrap();
441            assert_eq!(&*py_backed_str, "");
442        });
443    }
444
445    #[test]
446    fn py_backed_str() {
447        Python::attach(|py| {
448            let s = PyString::new(py, "hello");
449            let py_backed_str = s.extract::<PyBackedStr>().unwrap();
450            assert_eq!(&*py_backed_str, "hello");
451        });
452    }
453
454    #[test]
455    fn py_backed_str_try_from() {
456        Python::attach(|py| {
457            let s = PyString::new(py, "hello");
458            let py_backed_str = PyBackedStr::try_from(s).unwrap();
459            assert_eq!(&*py_backed_str, "hello");
460        });
461    }
462
463    #[test]
464    fn py_backed_str_into_pyobject() {
465        Python::attach(|py| {
466            let orig_str = PyString::new(py, "hello");
467            let py_backed_str = orig_str.extract::<PyBackedStr>().unwrap();
468            let new_str = py_backed_str.into_pyobject(py).unwrap();
469            assert_eq!(new_str.extract::<PyBackedStr>().unwrap(), "hello");
470            #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
471            assert!(new_str.is(&orig_str));
472        });
473    }
474
475    #[test]
476    fn py_backed_bytes_empty() {
477        Python::attach(|py| {
478            let b = PyBytes::new(py, b"");
479            let py_backed_bytes = b.extract::<PyBackedBytes>().unwrap();
480            assert_eq!(&*py_backed_bytes, b"");
481        });
482    }
483
484    #[test]
485    fn py_backed_bytes() {
486        Python::attach(|py| {
487            let b = PyBytes::new(py, b"abcde");
488            let py_backed_bytes = b.extract::<PyBackedBytes>().unwrap();
489            assert_eq!(&*py_backed_bytes, b"abcde");
490        });
491    }
492
493    #[test]
494    fn py_backed_bytes_from_bytes() {
495        Python::attach(|py| {
496            let b = PyBytes::new(py, b"abcde");
497            let py_backed_bytes = PyBackedBytes::from(b);
498            assert_eq!(&*py_backed_bytes, b"abcde");
499        });
500    }
501
502    #[test]
503    fn py_backed_bytes_from_bytearray() {
504        Python::attach(|py| {
505            let b = PyByteArray::new(py, b"abcde");
506            let py_backed_bytes = PyBackedBytes::from(b);
507            assert_eq!(&*py_backed_bytes, b"abcde");
508        });
509    }
510
511    #[test]
512    fn py_backed_bytes_into_pyobject() {
513        Python::attach(|py| {
514            let orig_bytes = PyBytes::new(py, b"abcde");
515            let py_backed_bytes = PyBackedBytes::from(orig_bytes.clone());
516            assert!((&py_backed_bytes)
517                .into_pyobject(py)
518                .unwrap()
519                .is(&orig_bytes));
520        });
521    }
522
523    #[test]
524    fn rust_backed_bytes_into_pyobject() {
525        Python::attach(|py| {
526            let orig_bytes = PyByteArray::new(py, b"abcde");
527            let rust_backed_bytes = PyBackedBytes::from(orig_bytes);
528            assert!(matches!(
529                rust_backed_bytes.storage,
530                PyBackedBytesStorage::Rust(_)
531            ));
532            let to_object = (&rust_backed_bytes).into_pyobject(py).unwrap();
533            assert!(&to_object.is_exact_instance_of::<PyBytes>());
534            assert_eq!(&to_object.extract::<PyBackedBytes>().unwrap(), b"abcde");
535        });
536    }
537
538    #[test]
539    fn test_backed_types_send_sync() {
540        assert!(value_of!(IsSend, PyBackedStr));
541        assert!(value_of!(IsSync, PyBackedStr));
542
543        assert!(value_of!(IsSend, PyBackedBytes));
544        assert!(value_of!(IsSync, PyBackedBytes));
545    }
546
547    #[cfg(feature = "py-clone")]
548    #[test]
549    fn test_backed_str_clone() {
550        Python::attach(|py| {
551            let s1: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
552            let s2 = s1.clone();
553            assert_eq!(s1, s2);
554
555            drop(s1);
556            assert_eq!(s2, "hello");
557        });
558    }
559
560    #[test]
561    fn test_backed_str_clone_ref() {
562        Python::attach(|py| {
563            let s1: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
564            let s2 = s1.clone_ref(py);
565            assert_eq!(s1, s2);
566            assert!(s1.storage.is(&s2.storage));
567
568            drop(s1);
569            assert_eq!(s2, "hello");
570        });
571    }
572
573    #[test]
574    fn test_backed_str_eq() {
575        Python::attach(|py| {
576            let s1: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
577            let s2: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
578            assert_eq!(s1, "hello");
579            assert_eq!(s1, s2);
580
581            let s3: PyBackedStr = PyString::new(py, "abcde").try_into().unwrap();
582            assert_eq!("abcde", s3);
583            assert_ne!(s1, s3);
584        });
585    }
586
587    #[test]
588    fn test_backed_str_hash() {
589        Python::attach(|py| {
590            let h = {
591                let mut hasher = DefaultHasher::new();
592                "abcde".hash(&mut hasher);
593                hasher.finish()
594            };
595
596            let s1: PyBackedStr = PyString::new(py, "abcde").try_into().unwrap();
597            let h1 = {
598                let mut hasher = DefaultHasher::new();
599                s1.hash(&mut hasher);
600                hasher.finish()
601            };
602
603            assert_eq!(h, h1);
604        });
605    }
606
607    #[test]
608    fn test_backed_str_ord() {
609        Python::attach(|py| {
610            let mut a = vec!["a", "c", "d", "b", "f", "g", "e"];
611            let mut b = a
612                .iter()
613                .map(|s| PyString::new(py, s).try_into().unwrap())
614                .collect::<Vec<PyBackedStr>>();
615
616            a.sort();
617            b.sort();
618
619            assert_eq!(a, b);
620        })
621    }
622
623    #[test]
624    fn test_backed_str_map_key() {
625        Python::attach(|py| {
626            use crate::platform::HashMap;
627
628            let mut map: HashMap<PyBackedStr, usize> = HashMap::new();
629            let s: PyBackedStr = PyString::new(py, "key1").try_into().unwrap();
630
631            map.insert(s, 1);
632
633            assert_eq!(map.get("key1"), Some(&1));
634        });
635    }
636
637    #[test]
638    fn test_backed_str_as_str() {
639        Python::attach(|py| {
640            let s: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
641            assert_eq!(s.as_str(), "hello");
642        });
643    }
644
645    #[test]
646    #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
647    fn test_backed_str_as_py_str() {
648        Python::attach(|py| {
649            let s: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
650            let py_str = s.as_py_str().bind(py);
651            assert!(py_str.is(&s.storage));
652            assert_eq!(py_str.to_str().unwrap(), "hello");
653        });
654    }
655
656    #[cfg(feature = "py-clone")]
657    #[test]
658    fn test_backed_bytes_from_bytes_clone() {
659        Python::attach(|py| {
660            let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into();
661            let b2 = b1.clone();
662            assert_eq!(b1, b2);
663
664            drop(b1);
665            assert_eq!(b2, b"abcde");
666        });
667    }
668
669    #[test]
670    fn test_backed_bytes_from_bytes_clone_ref() {
671        Python::attach(|py| {
672            let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into();
673            let b2 = b1.clone_ref(py);
674            assert_eq!(b1, b2);
675            let (PyBackedBytesStorage::Python(s1), PyBackedBytesStorage::Python(s2)) =
676                (&b1.storage, &b2.storage)
677            else {
678                panic!("Expected Python-backed bytes");
679            };
680            assert!(s1.is(s2));
681
682            drop(b1);
683            assert_eq!(b2, b"abcde");
684        });
685    }
686
687    #[cfg(feature = "py-clone")]
688    #[test]
689    fn test_backed_bytes_from_bytearray_clone() {
690        Python::attach(|py| {
691            let b1: PyBackedBytes = PyByteArray::new(py, b"abcde").into();
692            let b2 = b1.clone();
693            assert_eq!(b1, b2);
694
695            drop(b1);
696            assert_eq!(b2, b"abcde");
697        });
698    }
699
700    #[test]
701    fn test_backed_bytes_from_bytearray_clone_ref() {
702        Python::attach(|py| {
703            let b1: PyBackedBytes = PyByteArray::new(py, b"abcde").into();
704            let b2 = b1.clone_ref(py);
705            assert_eq!(b1, b2);
706            let (PyBackedBytesStorage::Rust(s1), PyBackedBytesStorage::Rust(s2)) =
707                (&b1.storage, &b2.storage)
708            else {
709                panic!("Expected Rust-backed bytes");
710            };
711            assert!(Arc::ptr_eq(s1, s2));
712
713            drop(b1);
714            assert_eq!(b2, b"abcde");
715        });
716    }
717
718    #[test]
719    fn test_backed_bytes_eq() {
720        Python::attach(|py| {
721            let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into();
722            let b2: PyBackedBytes = PyByteArray::new(py, b"abcde").into();
723
724            assert_eq!(b1, b"abcde");
725            assert_eq!(b1, b2);
726
727            let b3: PyBackedBytes = PyBytes::new(py, b"hello").into();
728            assert_eq!(b"hello", b3);
729            assert_ne!(b1, b3);
730        });
731    }
732
733    #[test]
734    fn test_backed_bytes_hash() {
735        Python::attach(|py| {
736            let h = {
737                let mut hasher = DefaultHasher::new();
738                b"abcde".hash(&mut hasher);
739                hasher.finish()
740            };
741
742            let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into();
743            let h1 = {
744                let mut hasher = DefaultHasher::new();
745                b1.hash(&mut hasher);
746                hasher.finish()
747            };
748
749            let b2: PyBackedBytes = PyByteArray::new(py, b"abcde").into();
750            let h2 = {
751                let mut hasher = DefaultHasher::new();
752                b2.hash(&mut hasher);
753                hasher.finish()
754            };
755
756            assert_eq!(h, h1);
757            assert_eq!(h, h2);
758        });
759    }
760
761    #[test]
762    fn test_backed_bytes_ord() {
763        Python::attach(|py| {
764            let mut a = vec![b"a", b"c", b"d", b"b", b"f", b"g", b"e"];
765            let mut b = a
766                .iter()
767                .map(|&b| PyBytes::new(py, b).into())
768                .collect::<Vec<PyBackedBytes>>();
769
770            a.sort();
771            b.sort();
772
773            assert_eq!(a, b);
774        })
775    }
776}