1use std::{ops::Deref, ptr::NonNull, sync::Arc};
4
5use crate::{
6 types::{
7 any::PyAnyMethods, bytearray::PyByteArrayMethods, bytes::PyBytesMethods,
8 string::PyStringMethods, PyByteArray, PyBytes, PyString,
9 },
10 Bound, DowncastError, FromPyObject, IntoPy, Py, PyAny, PyErr, PyResult, Python, ToPyObject,
11};
12
13#[cfg_attr(feature = "py-clone", derive(Clone))]
17pub struct PyBackedStr {
18 #[allow(dead_code)] storage: Py<PyAny>,
20 data: NonNull<str>,
21}
22
23impl Deref for PyBackedStr {
24 type Target = str;
25 fn deref(&self) -> &str {
26 unsafe { self.data.as_ref() }
28 }
29}
30
31impl AsRef<str> for PyBackedStr {
32 fn as_ref(&self) -> &str {
33 self
34 }
35}
36
37impl AsRef<[u8]> for PyBackedStr {
38 fn as_ref(&self) -> &[u8] {
39 self.as_bytes()
40 }
41}
42
43unsafe impl Send for PyBackedStr {}
46unsafe impl Sync for PyBackedStr {}
47
48impl 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
54impl_traits!(PyBackedStr, str);
55
56impl TryFrom<Bound<'_, PyString>> for PyBackedStr {
57 type Error = PyErr;
58 fn try_from(py_string: Bound<'_, PyString>) -> Result<Self, Self::Error> {
59 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
60 {
61 let s = py_string.to_str()?;
62 let data = NonNull::from(s);
63 Ok(Self {
64 storage: py_string.as_any().to_owned().unbind(),
65 data,
66 })
67 }
68 #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
69 {
70 let bytes = py_string.encode_utf8()?;
71 let s = unsafe { std::str::from_utf8_unchecked(bytes.as_bytes()) };
72 let data = NonNull::from(s);
73 Ok(Self {
74 storage: bytes.into_any().unbind(),
75 data,
76 })
77 }
78 }
79}
80
81impl FromPyObject<'_> for PyBackedStr {
82 fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
83 let py_string = obj.downcast::<PyString>()?.to_owned();
84 Self::try_from(py_string)
85 }
86}
87
88impl ToPyObject for PyBackedStr {
89 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
90 fn to_object(&self, py: Python<'_>) -> Py<PyAny> {
91 self.storage.clone_ref(py)
92 }
93 #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
94 fn to_object(&self, py: Python<'_>) -> Py<PyAny> {
95 PyString::new_bound(py, self).into_any().unbind()
96 }
97}
98
99impl IntoPy<Py<PyAny>> for PyBackedStr {
100 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
101 fn into_py(self, _py: Python<'_>) -> Py<PyAny> {
102 self.storage
103 }
104 #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
105 fn into_py(self, py: Python<'_>) -> Py<PyAny> {
106 PyString::new_bound(py, &self).into_any().unbind()
107 }
108}
109
110#[cfg_attr(feature = "py-clone", derive(Clone))]
114pub struct PyBackedBytes {
115 #[allow(dead_code)] storage: PyBackedBytesStorage,
117 data: NonNull<[u8]>,
118}
119
120#[allow(dead_code)]
121#[cfg_attr(feature = "py-clone", derive(Clone))]
122enum PyBackedBytesStorage {
123 Python(Py<PyBytes>),
124 Rust(Arc<[u8]>),
125}
126
127impl Deref for PyBackedBytes {
128 type Target = [u8];
129 fn deref(&self) -> &[u8] {
130 unsafe { self.data.as_ref() }
132 }
133}
134
135impl AsRef<[u8]> for PyBackedBytes {
136 fn as_ref(&self) -> &[u8] {
137 self
138 }
139}
140
141unsafe impl Send for PyBackedBytes {}
144unsafe impl Sync for PyBackedBytes {}
145
146impl<const N: usize> PartialEq<[u8; N]> for PyBackedBytes {
147 fn eq(&self, other: &[u8; N]) -> bool {
148 self.deref() == other
149 }
150}
151
152impl<const N: usize> PartialEq<PyBackedBytes> for [u8; N] {
153 fn eq(&self, other: &PyBackedBytes) -> bool {
154 self == other.deref()
155 }
156}
157
158impl<const N: usize> PartialEq<&[u8; N]> for PyBackedBytes {
159 fn eq(&self, other: &&[u8; N]) -> bool {
160 self.deref() == *other
161 }
162}
163
164impl<const N: usize> PartialEq<PyBackedBytes> for &[u8; N] {
165 fn eq(&self, other: &PyBackedBytes) -> bool {
166 self == &other.deref()
167 }
168}
169
170impl_traits!(PyBackedBytes, [u8]);
171
172impl From<Bound<'_, PyBytes>> for PyBackedBytes {
173 fn from(py_bytes: Bound<'_, PyBytes>) -> Self {
174 let b = py_bytes.as_bytes();
175 let data = NonNull::from(b);
176 Self {
177 storage: PyBackedBytesStorage::Python(py_bytes.to_owned().unbind()),
178 data,
179 }
180 }
181}
182
183impl From<Bound<'_, PyByteArray>> for PyBackedBytes {
184 fn from(py_bytearray: Bound<'_, PyByteArray>) -> Self {
185 let s = Arc::<[u8]>::from(py_bytearray.to_vec());
186 let data = NonNull::from(s.as_ref());
187 Self {
188 storage: PyBackedBytesStorage::Rust(s),
189 data,
190 }
191 }
192}
193
194impl FromPyObject<'_> for PyBackedBytes {
195 fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
196 if let Ok(bytes) = obj.downcast::<PyBytes>() {
197 Ok(Self::from(bytes.to_owned()))
198 } else if let Ok(bytearray) = obj.downcast::<PyByteArray>() {
199 Ok(Self::from(bytearray.to_owned()))
200 } else {
201 Err(DowncastError::new(obj, "`bytes` or `bytearray`").into())
202 }
203 }
204}
205
206impl ToPyObject for PyBackedBytes {
207 fn to_object(&self, py: Python<'_>) -> Py<PyAny> {
208 match &self.storage {
209 PyBackedBytesStorage::Python(bytes) => bytes.to_object(py),
210 PyBackedBytesStorage::Rust(bytes) => PyBytes::new_bound(py, bytes).into_any().unbind(),
211 }
212 }
213}
214
215impl IntoPy<Py<PyAny>> for PyBackedBytes {
216 fn into_py(self, py: Python<'_>) -> Py<PyAny> {
217 match self.storage {
218 PyBackedBytesStorage::Python(bytes) => bytes.into_any(),
219 PyBackedBytesStorage::Rust(bytes) => PyBytes::new_bound(py, &bytes).into_any().unbind(),
220 }
221 }
222}
223
224macro_rules! impl_traits {
225 ($slf:ty, $equiv:ty) => {
226 impl std::fmt::Debug for $slf {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 self.deref().fmt(f)
229 }
230 }
231
232 impl PartialEq for $slf {
233 fn eq(&self, other: &Self) -> bool {
234 self.deref() == other.deref()
235 }
236 }
237
238 impl PartialEq<$equiv> for $slf {
239 fn eq(&self, other: &$equiv) -> bool {
240 self.deref() == other
241 }
242 }
243
244 impl PartialEq<&$equiv> for $slf {
245 fn eq(&self, other: &&$equiv) -> bool {
246 self.deref() == *other
247 }
248 }
249
250 impl PartialEq<$slf> for $equiv {
251 fn eq(&self, other: &$slf) -> bool {
252 self == other.deref()
253 }
254 }
255
256 impl PartialEq<$slf> for &$equiv {
257 fn eq(&self, other: &$slf) -> bool {
258 self == &other.deref()
259 }
260 }
261
262 impl Eq for $slf {}
263
264 impl PartialOrd for $slf {
265 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
266 Some(self.cmp(other))
267 }
268 }
269
270 impl PartialOrd<$equiv> for $slf {
271 fn partial_cmp(&self, other: &$equiv) -> Option<std::cmp::Ordering> {
272 self.deref().partial_cmp(other)
273 }
274 }
275
276 impl PartialOrd<$slf> for $equiv {
277 fn partial_cmp(&self, other: &$slf) -> Option<std::cmp::Ordering> {
278 self.partial_cmp(other.deref())
279 }
280 }
281
282 impl Ord for $slf {
283 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
284 self.deref().cmp(other.deref())
285 }
286 }
287
288 impl std::hash::Hash for $slf {
289 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
290 self.deref().hash(state)
291 }
292 }
293 };
294}
295use impl_traits;
296
297#[cfg(test)]
298mod test {
299 use super::*;
300 use crate::Python;
301 use std::collections::hash_map::DefaultHasher;
302 use std::hash::{Hash, Hasher};
303
304 #[test]
305 fn py_backed_str_empty() {
306 Python::with_gil(|py| {
307 let s = PyString::new_bound(py, "");
308 let py_backed_str = s.extract::<PyBackedStr>().unwrap();
309 assert_eq!(&*py_backed_str, "");
310 });
311 }
312
313 #[test]
314 fn py_backed_str() {
315 Python::with_gil(|py| {
316 let s = PyString::new_bound(py, "hello");
317 let py_backed_str = s.extract::<PyBackedStr>().unwrap();
318 assert_eq!(&*py_backed_str, "hello");
319 });
320 }
321
322 #[test]
323 fn py_backed_str_try_from() {
324 Python::with_gil(|py| {
325 let s = PyString::new_bound(py, "hello");
326 let py_backed_str = PyBackedStr::try_from(s).unwrap();
327 assert_eq!(&*py_backed_str, "hello");
328 });
329 }
330
331 #[test]
332 fn py_backed_str_to_object() {
333 Python::with_gil(|py| {
334 let orig_str = PyString::new_bound(py, "hello");
335 let py_backed_str = orig_str.extract::<PyBackedStr>().unwrap();
336 let new_str = py_backed_str.to_object(py);
337 assert_eq!(new_str.extract::<PyBackedStr>(py).unwrap(), "hello");
338 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
339 assert!(new_str.is(&orig_str));
340 });
341 }
342
343 #[test]
344 fn py_backed_str_into_py() {
345 Python::with_gil(|py| {
346 let orig_str = PyString::new_bound(py, "hello");
347 let py_backed_str = orig_str.extract::<PyBackedStr>().unwrap();
348 let new_str = py_backed_str.into_py(py);
349 assert_eq!(new_str.extract::<PyBackedStr>(py).unwrap(), "hello");
350 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
351 assert!(new_str.is(&orig_str));
352 });
353 }
354
355 #[test]
356 fn py_backed_bytes_empty() {
357 Python::with_gil(|py| {
358 let b = PyBytes::new_bound(py, b"");
359 let py_backed_bytes = b.extract::<PyBackedBytes>().unwrap();
360 assert_eq!(&*py_backed_bytes, b"");
361 });
362 }
363
364 #[test]
365 fn py_backed_bytes() {
366 Python::with_gil(|py| {
367 let b = PyBytes::new_bound(py, b"abcde");
368 let py_backed_bytes = b.extract::<PyBackedBytes>().unwrap();
369 assert_eq!(&*py_backed_bytes, b"abcde");
370 });
371 }
372
373 #[test]
374 fn py_backed_bytes_from_bytes() {
375 Python::with_gil(|py| {
376 let b = PyBytes::new_bound(py, b"abcde");
377 let py_backed_bytes = PyBackedBytes::from(b);
378 assert_eq!(&*py_backed_bytes, b"abcde");
379 });
380 }
381
382 #[test]
383 fn py_backed_bytes_from_bytearray() {
384 Python::with_gil(|py| {
385 let b = PyByteArray::new_bound(py, b"abcde");
386 let py_backed_bytes = PyBackedBytes::from(b);
387 assert_eq!(&*py_backed_bytes, b"abcde");
388 });
389 }
390
391 #[test]
392 fn py_backed_bytes_into_py() {
393 Python::with_gil(|py| {
394 let orig_bytes = PyBytes::new_bound(py, b"abcde");
395 let py_backed_bytes = PyBackedBytes::from(orig_bytes.clone());
396 assert!(py_backed_bytes.to_object(py).is(&orig_bytes));
397 assert!(py_backed_bytes.into_py(py).is(&orig_bytes));
398 });
399 }
400
401 #[test]
402 fn rust_backed_bytes_into_py() {
403 Python::with_gil(|py| {
404 let orig_bytes = PyByteArray::new_bound(py, b"abcde");
405 let rust_backed_bytes = PyBackedBytes::from(orig_bytes);
406 assert!(matches!(
407 rust_backed_bytes.storage,
408 PyBackedBytesStorage::Rust(_)
409 ));
410 let to_object = rust_backed_bytes.to_object(py).into_bound(py);
411 assert!(&to_object.is_exact_instance_of::<PyBytes>());
412 assert_eq!(&to_object.extract::<PyBackedBytes>().unwrap(), b"abcde");
413 let into_py = rust_backed_bytes.into_py(py).into_bound(py);
414 assert!(&into_py.is_exact_instance_of::<PyBytes>());
415 assert_eq!(&into_py.extract::<PyBackedBytes>().unwrap(), b"abcde");
416 });
417 }
418
419 #[test]
420 fn test_backed_types_send_sync() {
421 fn is_send<T: Send>() {}
422 fn is_sync<T: Sync>() {}
423
424 is_send::<PyBackedStr>();
425 is_sync::<PyBackedStr>();
426
427 is_send::<PyBackedBytes>();
428 is_sync::<PyBackedBytes>();
429 }
430
431 #[cfg(feature = "py-clone")]
432 #[test]
433 fn test_backed_str_clone() {
434 Python::with_gil(|py| {
435 let s1: PyBackedStr = PyString::new_bound(py, "hello").try_into().unwrap();
436 let s2 = s1.clone();
437 assert_eq!(s1, s2);
438
439 drop(s1);
440 assert_eq!(s2, "hello");
441 });
442 }
443
444 #[test]
445 fn test_backed_str_eq() {
446 Python::with_gil(|py| {
447 let s1: PyBackedStr = PyString::new_bound(py, "hello").try_into().unwrap();
448 let s2: PyBackedStr = PyString::new_bound(py, "hello").try_into().unwrap();
449 assert_eq!(s1, "hello");
450 assert_eq!(s1, s2);
451
452 let s3: PyBackedStr = PyString::new_bound(py, "abcde").try_into().unwrap();
453 assert_eq!("abcde", s3);
454 assert_ne!(s1, s3);
455 });
456 }
457
458 #[test]
459 fn test_backed_str_hash() {
460 Python::with_gil(|py| {
461 let h = {
462 let mut hasher = DefaultHasher::new();
463 "abcde".hash(&mut hasher);
464 hasher.finish()
465 };
466
467 let s1: PyBackedStr = PyString::new_bound(py, "abcde").try_into().unwrap();
468 let h1 = {
469 let mut hasher = DefaultHasher::new();
470 s1.hash(&mut hasher);
471 hasher.finish()
472 };
473
474 assert_eq!(h, h1);
475 });
476 }
477
478 #[test]
479 fn test_backed_str_ord() {
480 Python::with_gil(|py| {
481 let mut a = vec!["a", "c", "d", "b", "f", "g", "e"];
482 let mut b = a
483 .iter()
484 .map(|s| PyString::new_bound(py, s).try_into().unwrap())
485 .collect::<Vec<PyBackedStr>>();
486
487 a.sort();
488 b.sort();
489
490 assert_eq!(a, b);
491 })
492 }
493
494 #[cfg(feature = "py-clone")]
495 #[test]
496 fn test_backed_bytes_from_bytes_clone() {
497 Python::with_gil(|py| {
498 let b1: PyBackedBytes = PyBytes::new_bound(py, b"abcde").into();
499 let b2 = b1.clone();
500 assert_eq!(b1, b2);
501
502 drop(b1);
503 assert_eq!(b2, b"abcde");
504 });
505 }
506
507 #[cfg(feature = "py-clone")]
508 #[test]
509 fn test_backed_bytes_from_bytearray_clone() {
510 Python::with_gil(|py| {
511 let b1: PyBackedBytes = PyByteArray::new_bound(py, b"abcde").into();
512 let b2 = b1.clone();
513 assert_eq!(b1, b2);
514
515 drop(b1);
516 assert_eq!(b2, b"abcde");
517 });
518 }
519
520 #[test]
521 fn test_backed_bytes_eq() {
522 Python::with_gil(|py| {
523 let b1: PyBackedBytes = PyBytes::new_bound(py, b"abcde").into();
524 let b2: PyBackedBytes = PyByteArray::new_bound(py, b"abcde").into();
525
526 assert_eq!(b1, b"abcde");
527 assert_eq!(b1, b2);
528
529 let b3: PyBackedBytes = PyBytes::new_bound(py, b"hello").into();
530 assert_eq!(b"hello", b3);
531 assert_ne!(b1, b3);
532 });
533 }
534
535 #[test]
536 fn test_backed_bytes_hash() {
537 Python::with_gil(|py| {
538 let h = {
539 let mut hasher = DefaultHasher::new();
540 b"abcde".hash(&mut hasher);
541 hasher.finish()
542 };
543
544 let b1: PyBackedBytes = PyBytes::new_bound(py, b"abcde").into();
545 let h1 = {
546 let mut hasher = DefaultHasher::new();
547 b1.hash(&mut hasher);
548 hasher.finish()
549 };
550
551 let b2: PyBackedBytes = PyByteArray::new_bound(py, b"abcde").into();
552 let h2 = {
553 let mut hasher = DefaultHasher::new();
554 b2.hash(&mut hasher);
555 hasher.finish()
556 };
557
558 assert_eq!(h, h1);
559 assert_eq!(h, h2);
560 });
561 }
562
563 #[test]
564 fn test_backed_bytes_ord() {
565 Python::with_gil(|py| {
566 let mut a = vec![b"a", b"c", b"d", b"b", b"f", b"g", b"e"];
567 let mut b = a
568 .iter()
569 .map(|&b| PyBytes::new_bound(py, b).into())
570 .collect::<Vec<PyBackedBytes>>();
571
572 a.sort();
573 b.sort();
574
575 assert_eq!(a, b);
576 })
577 }
578}