Skip to content

Commit 63ba371

Browse files
authored
added various std traits for PyBackedStr and PyBackedBytes (#4020)
* added various std traits for `PyBackedStr` and `PyBackedBytes` * add newsfragment * add tests
1 parent 336b1c9 commit 63ba371

File tree

2 files changed

+261
-3
lines changed

2 files changed

+261
-3
lines changed

newsfragments/4020.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Adds `Clone`, `Debug`, `PartialEq`, `Eq`, `PartialOrd`, `Ord` and `Hash` implementation for `PyBackedBytes` and `PyBackedStr`, and `Display` for `PyBackedStr`.

src/pybacked.rs

Lines changed: 260 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Contains types for working with Python objects that own the underlying data.
22
3-
use std::{ops::Deref, ptr::NonNull};
3+
use std::{ops::Deref, ptr::NonNull, sync::Arc};
44

55
use crate::{
66
types::{
@@ -13,6 +13,7 @@ use crate::{
1313
/// A wrapper around `str` where the storage is owned by a Python `bytes` or `str` object.
1414
///
1515
/// This type gives access to the underlying data via a `Deref` implementation.
16+
#[derive(Clone)]
1617
pub struct PyBackedStr {
1718
#[allow(dead_code)] // only held so that the storage is not dropped
1819
storage: Py<PyAny>,
@@ -44,6 +45,14 @@ impl AsRef<[u8]> for PyBackedStr {
4445
unsafe impl Send for PyBackedStr {}
4546
unsafe impl Sync for PyBackedStr {}
4647

48+
impl std::fmt::Display for PyBackedStr {
49+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50+
self.deref().fmt(f)
51+
}
52+
}
53+
54+
impl_traits!(PyBackedStr, str);
55+
4756
impl TryFrom<Bound<'_, PyString>> for PyBackedStr {
4857
type Error = PyErr;
4958
fn try_from(py_string: Bound<'_, PyString>) -> Result<Self, Self::Error> {
@@ -79,16 +88,18 @@ impl FromPyObject<'_> for PyBackedStr {
7988
/// A wrapper around `[u8]` where the storage is either owned by a Python `bytes` object, or a Rust `Box<[u8]>`.
8089
///
8190
/// This type gives access to the underlying data via a `Deref` implementation.
91+
#[derive(Clone)]
8292
pub struct PyBackedBytes {
8393
#[allow(dead_code)] // only held so that the storage is not dropped
8494
storage: PyBackedBytesStorage,
8595
data: NonNull<[u8]>,
8696
}
8797

8898
#[allow(dead_code)]
99+
#[derive(Clone)]
89100
enum PyBackedBytesStorage {
90101
Python(Py<PyBytes>),
91-
Rust(Box<[u8]>),
102+
Rust(Arc<[u8]>),
92103
}
93104

94105
impl Deref for PyBackedBytes {
@@ -110,6 +121,32 @@ impl AsRef<[u8]> for PyBackedBytes {
110121
unsafe impl Send for PyBackedBytes {}
111122
unsafe impl Sync for PyBackedBytes {}
112123

124+
impl<const N: usize> PartialEq<[u8; N]> for PyBackedBytes {
125+
fn eq(&self, other: &[u8; N]) -> bool {
126+
self.deref() == other
127+
}
128+
}
129+
130+
impl<const N: usize> PartialEq<PyBackedBytes> for [u8; N] {
131+
fn eq(&self, other: &PyBackedBytes) -> bool {
132+
self == other.deref()
133+
}
134+
}
135+
136+
impl<const N: usize> PartialEq<&[u8; N]> for PyBackedBytes {
137+
fn eq(&self, other: &&[u8; N]) -> bool {
138+
self.deref() == *other
139+
}
140+
}
141+
142+
impl<const N: usize> PartialEq<PyBackedBytes> for &[u8; N] {
143+
fn eq(&self, other: &PyBackedBytes) -> bool {
144+
self == &other.deref()
145+
}
146+
}
147+
148+
impl_traits!(PyBackedBytes, [u8]);
149+
113150
impl From<Bound<'_, PyBytes>> for PyBackedBytes {
114151
fn from(py_bytes: Bound<'_, PyBytes>) -> Self {
115152
let b = py_bytes.as_bytes();
@@ -123,7 +160,7 @@ impl From<Bound<'_, PyBytes>> for PyBackedBytes {
123160

124161
impl From<Bound<'_, PyByteArray>> for PyBackedBytes {
125162
fn from(py_bytearray: Bound<'_, PyByteArray>) -> Self {
126-
let s = py_bytearray.to_vec().into_boxed_slice();
163+
let s = Arc::<[u8]>::from(py_bytearray.to_vec());
127164
let data = NonNull::from(s.as_ref());
128165
Self {
129166
storage: PyBackedBytesStorage::Rust(s),
@@ -144,10 +181,85 @@ impl FromPyObject<'_> for PyBackedBytes {
144181
}
145182
}
146183

184+
macro_rules! impl_traits {
185+
($slf:ty, $equiv:ty) => {
186+
impl std::fmt::Debug for $slf {
187+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188+
self.deref().fmt(f)
189+
}
190+
}
191+
192+
impl PartialEq for $slf {
193+
fn eq(&self, other: &Self) -> bool {
194+
self.deref() == other.deref()
195+
}
196+
}
197+
198+
impl PartialEq<$equiv> for $slf {
199+
fn eq(&self, other: &$equiv) -> bool {
200+
self.deref() == other
201+
}
202+
}
203+
204+
impl PartialEq<&$equiv> for $slf {
205+
fn eq(&self, other: &&$equiv) -> bool {
206+
self.deref() == *other
207+
}
208+
}
209+
210+
impl PartialEq<$slf> for $equiv {
211+
fn eq(&self, other: &$slf) -> bool {
212+
self == other.deref()
213+
}
214+
}
215+
216+
impl PartialEq<$slf> for &$equiv {
217+
fn eq(&self, other: &$slf) -> bool {
218+
self == &other.deref()
219+
}
220+
}
221+
222+
impl Eq for $slf {}
223+
224+
impl PartialOrd for $slf {
225+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
226+
Some(self.cmp(other))
227+
}
228+
}
229+
230+
impl PartialOrd<$equiv> for $slf {
231+
fn partial_cmp(&self, other: &$equiv) -> Option<std::cmp::Ordering> {
232+
self.deref().partial_cmp(other)
233+
}
234+
}
235+
236+
impl PartialOrd<$slf> for $equiv {
237+
fn partial_cmp(&self, other: &$slf) -> Option<std::cmp::Ordering> {
238+
self.partial_cmp(other.deref())
239+
}
240+
}
241+
242+
impl Ord for $slf {
243+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
244+
self.deref().cmp(other.deref())
245+
}
246+
}
247+
248+
impl std::hash::Hash for $slf {
249+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
250+
self.deref().hash(state)
251+
}
252+
}
253+
};
254+
}
255+
use impl_traits;
256+
147257
#[cfg(test)]
148258
mod test {
149259
use super::*;
150260
use crate::Python;
261+
use std::collections::hash_map::DefaultHasher;
262+
use std::hash::{Hash, Hasher};
151263

152264
#[test]
153265
fn py_backed_str_empty() {
@@ -223,4 +335,149 @@ mod test {
223335
is_send::<PyBackedBytes>();
224336
is_sync::<PyBackedBytes>();
225337
}
338+
339+
#[test]
340+
fn test_backed_str_clone() {
341+
Python::with_gil(|py| {
342+
let s1: PyBackedStr = PyString::new_bound(py, "hello").try_into().unwrap();
343+
let s2 = s1.clone();
344+
assert_eq!(s1, s2);
345+
346+
drop(s1);
347+
assert_eq!(s2, "hello");
348+
});
349+
}
350+
351+
#[test]
352+
fn test_backed_str_eq() {
353+
Python::with_gil(|py| {
354+
let s1: PyBackedStr = PyString::new_bound(py, "hello").try_into().unwrap();
355+
let s2: PyBackedStr = PyString::new_bound(py, "hello").try_into().unwrap();
356+
assert_eq!(s1, "hello");
357+
assert_eq!(s1, s2);
358+
359+
let s3: PyBackedStr = PyString::new_bound(py, "abcde").try_into().unwrap();
360+
assert_eq!("abcde", s3);
361+
assert_ne!(s1, s3);
362+
});
363+
}
364+
365+
#[test]
366+
fn test_backed_str_hash() {
367+
Python::with_gil(|py| {
368+
let h = {
369+
let mut hasher = DefaultHasher::new();
370+
"abcde".hash(&mut hasher);
371+
hasher.finish()
372+
};
373+
374+
let s1: PyBackedStr = PyString::new_bound(py, "abcde").try_into().unwrap();
375+
let h1 = {
376+
let mut hasher = DefaultHasher::new();
377+
s1.hash(&mut hasher);
378+
hasher.finish()
379+
};
380+
381+
assert_eq!(h, h1);
382+
});
383+
}
384+
385+
#[test]
386+
fn test_backed_str_ord() {
387+
Python::with_gil(|py| {
388+
let mut a = vec!["a", "c", "d", "b", "f", "g", "e"];
389+
let mut b = a
390+
.iter()
391+
.map(|s| PyString::new_bound(py, s).try_into().unwrap())
392+
.collect::<Vec<PyBackedStr>>();
393+
394+
a.sort();
395+
b.sort();
396+
397+
assert_eq!(a, b);
398+
})
399+
}
400+
401+
#[test]
402+
fn test_backed_bytes_from_bytes_clone() {
403+
Python::with_gil(|py| {
404+
let b1: PyBackedBytes = PyBytes::new_bound(py, b"abcde").into();
405+
let b2 = b1.clone();
406+
assert_eq!(b1, b2);
407+
408+
drop(b1);
409+
assert_eq!(b2, b"abcde");
410+
});
411+
}
412+
413+
#[test]
414+
fn test_backed_bytes_from_bytearray_clone() {
415+
Python::with_gil(|py| {
416+
let b1: PyBackedBytes = PyByteArray::new_bound(py, b"abcde").into();
417+
let b2 = b1.clone();
418+
assert_eq!(b1, b2);
419+
420+
drop(b1);
421+
assert_eq!(b2, b"abcde");
422+
});
423+
}
424+
425+
#[test]
426+
fn test_backed_bytes_eq() {
427+
Python::with_gil(|py| {
428+
let b1: PyBackedBytes = PyBytes::new_bound(py, b"abcde").into();
429+
let b2: PyBackedBytes = PyByteArray::new_bound(py, b"abcde").into();
430+
431+
assert_eq!(b1, b"abcde");
432+
assert_eq!(b1, b2);
433+
434+
let b3: PyBackedBytes = PyBytes::new_bound(py, b"hello").into();
435+
assert_eq!(b"hello", b3);
436+
assert_ne!(b1, b3);
437+
});
438+
}
439+
440+
#[test]
441+
fn test_backed_bytes_hash() {
442+
Python::with_gil(|py| {
443+
let h = {
444+
let mut hasher = DefaultHasher::new();
445+
b"abcde".hash(&mut hasher);
446+
hasher.finish()
447+
};
448+
449+
let b1: PyBackedBytes = PyBytes::new_bound(py, b"abcde").into();
450+
let h1 = {
451+
let mut hasher = DefaultHasher::new();
452+
b1.hash(&mut hasher);
453+
hasher.finish()
454+
};
455+
456+
let b2: PyBackedBytes = PyByteArray::new_bound(py, b"abcde").into();
457+
let h2 = {
458+
let mut hasher = DefaultHasher::new();
459+
b2.hash(&mut hasher);
460+
hasher.finish()
461+
};
462+
463+
assert_eq!(h, h1);
464+
assert_eq!(h, h2);
465+
});
466+
}
467+
468+
#[test]
469+
fn test_backed_bytes_ord() {
470+
Python::with_gil(|py| {
471+
let mut a = vec![b"a", b"c", b"d", b"b", b"f", b"g", b"e"];
472+
let mut b = a
473+
.iter()
474+
.map(|&b| PyBytes::new_bound(py, b).into())
475+
.collect::<Vec<PyBackedBytes>>();
476+
477+
a.sort();
478+
b.sort();
479+
480+
assert_eq!(a, b);
481+
})
482+
}
226483
}

0 commit comments

Comments
 (0)