Skip to main content

pyo3/
pybacked.rs

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