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