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