1#[cfg(not(Py_LIMITED_API))]
2use crate::exceptions::PyUnicodeDecodeError;
3use crate::ffi_ptr_ext::FfiPtrExt;
4use crate::instance::Borrowed;
5use crate::py_result_ext::PyResultExt;
6use crate::types::bytes::PyBytesMethods;
7use crate::types::PyBytes;
8use crate::{ffi, Bound, Py, PyAny, PyResult, Python};
9use std::borrow::Cow;
10use std::ffi::CStr;
11use std::{fmt, str};
12
13#[cfg(not(Py_LIMITED_API))]
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum PyStringData<'a> {
20 Ucs1(&'a [u8]),
22
23 Ucs2(&'a [u16]),
25
26 Ucs4(&'a [u32]),
28}
29
30#[cfg(not(Py_LIMITED_API))]
31impl<'a> PyStringData<'a> {
32 pub fn as_bytes(&self) -> &[u8] {
34 match self {
35 Self::Ucs1(s) => s,
36 Self::Ucs2(s) => unsafe {
37 std::slice::from_raw_parts(s.as_ptr().cast(), s.len() * self.value_width_bytes())
38 },
39 Self::Ucs4(s) => unsafe {
40 std::slice::from_raw_parts(s.as_ptr().cast(), s.len() * self.value_width_bytes())
41 },
42 }
43 }
44
45 #[inline]
47 pub fn value_width_bytes(&self) -> usize {
48 match self {
49 Self::Ucs1(_) => 1,
50 Self::Ucs2(_) => 2,
51 Self::Ucs4(_) => 4,
52 }
53 }
54
55 pub fn to_string(self, py: Python<'_>) -> PyResult<Cow<'a, str>> {
65 match self {
66 Self::Ucs1(data) => match str::from_utf8(data) {
67 Ok(s) => Ok(Cow::Borrowed(s)),
68 Err(e) => Err(PyUnicodeDecodeError::new_utf8(py, data, e)?.into()),
69 },
70 Self::Ucs2(data) => match String::from_utf16(data) {
71 Ok(s) => Ok(Cow::Owned(s)),
72 Err(e) => {
73 let mut message = e.to_string().as_bytes().to_vec();
74 message.push(0);
75
76 Err(PyUnicodeDecodeError::new(
77 py,
78 c"utf-16",
79 self.as_bytes(),
80 0..self.as_bytes().len(),
81 CStr::from_bytes_with_nul(&message).unwrap(),
82 )?
83 .into())
84 }
85 },
86 Self::Ucs4(data) => match data.iter().map(|&c| std::char::from_u32(c)).collect() {
87 Some(s) => Ok(Cow::Owned(s)),
88 None => Err(PyUnicodeDecodeError::new(
89 py,
90 c"utf-32",
91 self.as_bytes(),
92 0..self.as_bytes().len(),
93 c"error converting utf-32",
94 )?
95 .into()),
96 },
97 }
98 }
99
100 pub fn to_string_lossy(self) -> Cow<'a, str> {
109 match self {
110 Self::Ucs1(data) => String::from_utf8_lossy(data),
111 Self::Ucs2(data) => Cow::Owned(String::from_utf16_lossy(data)),
112 Self::Ucs4(data) => Cow::Owned(
113 data.iter()
114 .map(|&c| std::char::from_u32(c).unwrap_or('\u{FFFD}'))
115 .collect(),
116 ),
117 }
118 }
119}
120
121#[repr(transparent)]
153pub struct PyString(PyAny);
154
155pyobject_native_type_core!(PyString, pyobject_native_static_type_object!(ffi::PyUnicode_Type), "builtins", "str", #checkfunction=ffi::PyUnicode_Check);
156
157impl PyString {
158 pub fn new<'py>(py: Python<'py>, s: &str) -> Bound<'py, PyString> {
162 let ptr = s.as_ptr().cast();
163 let len = s.len() as ffi::Py_ssize_t;
164 unsafe {
165 ffi::PyUnicode_FromStringAndSize(ptr, len)
166 .assume_owned(py)
167 .cast_into_unchecked()
168 }
169 }
170
171 pub fn from_bytes<'py>(py: Python<'py>, s: &[u8]) -> PyResult<Bound<'py, PyString>> {
176 let ptr = s.as_ptr().cast();
177 let len = s.len() as ffi::Py_ssize_t;
178 unsafe {
179 ffi::PyUnicode_FromStringAndSize(ptr, len)
180 .assume_owned_or_err(py)
181 .cast_into_unchecked()
182 }
183 }
184
185 pub fn intern<'py>(py: Python<'py>, s: &str) -> Bound<'py, PyString> {
194 let ptr = s.as_ptr().cast();
195 let len = s.len() as ffi::Py_ssize_t;
196 unsafe {
197 let mut ob = ffi::PyUnicode_FromStringAndSize(ptr, len);
198 if !ob.is_null() {
199 ffi::PyUnicode_InternInPlace(&mut ob);
200 }
201 ob.assume_owned(py).cast_into_unchecked()
202 }
203 }
204
205 pub fn from_encoded_object<'py>(
216 src: &Bound<'py, PyAny>,
217 encoding: Option<&CStr>,
218 errors: Option<&CStr>,
219 ) -> PyResult<Bound<'py, PyString>> {
220 let encoding = encoding.map_or(std::ptr::null(), CStr::as_ptr);
221 let errors = errors.map_or(std::ptr::null(), CStr::as_ptr);
222 unsafe {
228 ffi::PyUnicode_FromEncodedObject(src.as_ptr(), encoding, errors)
229 .assume_owned_or_err(src.py())
230 .cast_into_unchecked()
231 }
232 }
233
234 #[inline]
238 pub fn from_fmt<'py>(
239 py: Python<'py>,
240 args: fmt::Arguments<'_>,
241 ) -> PyResult<Bound<'py, PyString>> {
242 if let Some(static_string) = args.as_str() {
243 return Ok(PyString::new(py, static_string));
244 };
245
246 #[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
247 {
248 use crate::fmt::PyUnicodeWriter;
249 use std::fmt::Write as _;
250
251 let mut writer = PyUnicodeWriter::new(py)?;
252 writer
253 .write_fmt(args)
254 .map_err(|_| writer.take_error().expect("expected error"))?;
255 writer.into_py_string()
256 }
257
258 #[cfg(any(not(Py_3_14), Py_LIMITED_API))]
259 {
260 Ok(PyString::new(py, &format!("{args}")))
261 }
262 }
263}
264
265#[doc(alias = "PyString")]
271pub trait PyStringMethods<'py>: crate::sealed::Sealed {
272 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
277 fn to_str(&self) -> PyResult<&str>;
278
279 fn to_cow(&self) -> PyResult<Cow<'_, str>>;
284
285 fn to_string_lossy(&self) -> Cow<'_, str>;
290
291 fn encode_utf8(&self) -> PyResult<Bound<'py, PyBytes>>;
293
294 #[cfg(not(any(Py_LIMITED_API, GraalPy, PyPy)))]
309 unsafe fn data(&self) -> PyResult<PyStringData<'_>>;
310}
311
312impl<'py> PyStringMethods<'py> for Bound<'py, PyString> {
313 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
314 fn to_str(&self) -> PyResult<&str> {
315 self.as_borrowed().to_str()
316 }
317
318 fn to_cow(&self) -> PyResult<Cow<'_, str>> {
319 self.as_borrowed().to_cow()
320 }
321
322 fn to_string_lossy(&self) -> Cow<'_, str> {
323 self.as_borrowed().to_string_lossy()
324 }
325
326 fn encode_utf8(&self) -> PyResult<Bound<'py, PyBytes>> {
327 unsafe {
328 ffi::PyUnicode_AsUTF8String(self.as_ptr())
329 .assume_owned_or_err(self.py())
330 .cast_into_unchecked::<PyBytes>()
331 }
332 }
333
334 #[cfg(not(any(Py_LIMITED_API, GraalPy, PyPy)))]
335 unsafe fn data(&self) -> PyResult<PyStringData<'_>> {
336 unsafe { self.as_borrowed().data() }
337 }
338}
339
340impl<'a> Borrowed<'a, '_, PyString> {
341 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
342 pub(crate) fn to_str(self) -> PyResult<&'a str> {
343 let mut size: ffi::Py_ssize_t = 0;
345 let data: *const u8 =
346 unsafe { ffi::PyUnicode_AsUTF8AndSize(self.as_ptr(), &mut size).cast() };
347 if data.is_null() {
348 Err(crate::PyErr::fetch(self.py()))
349 } else {
350 Ok(unsafe {
351 std::str::from_utf8_unchecked(std::slice::from_raw_parts(data, size as usize))
352 })
353 }
354 }
355
356 pub(crate) fn to_cow(self) -> PyResult<Cow<'a, str>> {
357 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
360 {
361 self.to_str().map(Cow::Borrowed)
362 }
363
364 #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
365 {
366 let bytes = self.encode_utf8()?;
367 Ok(Cow::Owned(
368 unsafe { str::from_utf8_unchecked(bytes.as_bytes()) }.to_owned(),
369 ))
370 }
371 }
372
373 fn to_string_lossy(self) -> Cow<'a, str> {
374 let ptr = self.as_ptr();
375 let py = self.py();
376
377 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
378 if let Ok(s) = self.to_str() {
379 return Cow::Borrowed(s);
380 }
381
382 let bytes = unsafe {
383 ffi::PyUnicode_AsEncodedString(ptr, c"utf-8".as_ptr(), c"surrogatepass".as_ptr())
384 .assume_owned(py)
385 .cast_into_unchecked::<PyBytes>()
386 };
387 Cow::Owned(String::from_utf8_lossy(bytes.as_bytes()).into_owned())
388 }
389
390 #[cfg(not(any(Py_LIMITED_API, GraalPy, PyPy)))]
391 unsafe fn data(self) -> PyResult<PyStringData<'a>> {
392 unsafe {
393 let ptr = self.as_ptr();
394
395 #[cfg(not(Py_3_12))]
396 #[allow(deprecated)]
397 {
398 let ready = ffi::PyUnicode_READY(ptr);
399 if ready != 0 {
400 return Err(crate::PyErr::fetch(self.py()));
402 }
403 }
404
405 let length = ffi::PyUnicode_GET_LENGTH(ptr) as usize;
409 let raw_data = ffi::PyUnicode_DATA(ptr);
410 let kind = ffi::PyUnicode_KIND(ptr);
411
412 match kind {
413 ffi::PyUnicode_1BYTE_KIND => Ok(PyStringData::Ucs1(std::slice::from_raw_parts(
414 raw_data as *const u8,
415 length,
416 ))),
417 ffi::PyUnicode_2BYTE_KIND => Ok(PyStringData::Ucs2(std::slice::from_raw_parts(
418 raw_data as *const u16,
419 length,
420 ))),
421 ffi::PyUnicode_4BYTE_KIND => Ok(PyStringData::Ucs4(std::slice::from_raw_parts(
422 raw_data as *const u32,
423 length,
424 ))),
425 _ => unreachable!(),
426 }
427 }
428 }
429}
430
431impl Py<PyString> {
432 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
440 pub fn to_str<'a>(&'a self, py: Python<'_>) -> PyResult<&'a str> {
441 self.bind_borrowed(py).to_str()
442 }
443
444 pub fn to_cow<'a>(&'a self, py: Python<'_>) -> PyResult<Cow<'a, str>> {
452 self.bind_borrowed(py).to_cow()
453 }
454
455 pub fn to_string_lossy<'a>(&'a self, py: Python<'_>) -> Cow<'a, str> {
463 self.bind_borrowed(py).to_string_lossy()
464 }
465}
466
467impl PartialEq<str> for Bound<'_, PyString> {
471 #[inline]
472 fn eq(&self, other: &str) -> bool {
473 self.as_borrowed() == *other
474 }
475}
476
477impl PartialEq<&'_ str> for Bound<'_, PyString> {
481 #[inline]
482 fn eq(&self, other: &&str) -> bool {
483 self.as_borrowed() == **other
484 }
485}
486
487impl PartialEq<Bound<'_, PyString>> for str {
491 #[inline]
492 fn eq(&self, other: &Bound<'_, PyString>) -> bool {
493 *self == other.as_borrowed()
494 }
495}
496
497impl PartialEq<&'_ Bound<'_, PyString>> for str {
501 #[inline]
502 fn eq(&self, other: &&Bound<'_, PyString>) -> bool {
503 *self == other.as_borrowed()
504 }
505}
506
507impl PartialEq<Bound<'_, PyString>> for &'_ str {
511 #[inline]
512 fn eq(&self, other: &Bound<'_, PyString>) -> bool {
513 **self == other.as_borrowed()
514 }
515}
516
517impl PartialEq<str> for &'_ Bound<'_, PyString> {
521 #[inline]
522 fn eq(&self, other: &str) -> bool {
523 self.as_borrowed() == other
524 }
525}
526
527impl PartialEq<str> for Borrowed<'_, '_, PyString> {
531 #[inline]
532 fn eq(&self, other: &str) -> bool {
533 #[cfg(not(Py_3_13))]
534 {
535 self.to_cow().is_ok_and(|s| s == other)
536 }
537
538 #[cfg(Py_3_13)]
539 unsafe {
540 ffi::PyUnicode_EqualToUTF8AndSize(
541 self.as_ptr(),
542 other.as_ptr().cast(),
543 other.len() as _,
544 ) == 1
545 }
546 }
547}
548
549impl PartialEq<&str> for Borrowed<'_, '_, PyString> {
553 #[inline]
554 fn eq(&self, other: &&str) -> bool {
555 *self == **other
556 }
557}
558
559impl PartialEq<Borrowed<'_, '_, PyString>> for str {
563 #[inline]
564 fn eq(&self, other: &Borrowed<'_, '_, PyString>) -> bool {
565 other == self
566 }
567}
568
569impl PartialEq<Borrowed<'_, '_, PyString>> for &'_ str {
573 #[inline]
574 fn eq(&self, other: &Borrowed<'_, '_, PyString>) -> bool {
575 other == self
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582 use crate::{exceptions::PyLookupError, types::PyAnyMethods as _, IntoPyObject};
583
584 #[test]
585 fn test_to_cow_utf8() {
586 Python::attach(|py| {
587 let s = "ascii 🐈";
588 let py_string = PyString::new(py, s);
589 assert_eq!(s, py_string.to_cow().unwrap());
590 })
591 }
592
593 #[test]
594 fn test_to_cow_surrogate() {
595 Python::attach(|py| {
596 let py_string = py
597 .eval(cr"'\ud800'", None, None)
598 .unwrap()
599 .cast_into::<PyString>()
600 .unwrap();
601 assert!(py_string.to_cow().is_err());
602 })
603 }
604
605 #[test]
606 fn test_to_cow_unicode() {
607 Python::attach(|py| {
608 let s = "哈哈🐈";
609 let py_string = PyString::new(py, s);
610 assert_eq!(s, py_string.to_cow().unwrap());
611 })
612 }
613
614 #[test]
615 fn test_encode_utf8_unicode() {
616 Python::attach(|py| {
617 let s = "哈哈🐈";
618 let obj = PyString::new(py, s);
619 assert_eq!(s.as_bytes(), obj.encode_utf8().unwrap().as_bytes());
620 })
621 }
622
623 #[test]
624 fn test_encode_utf8_surrogate() {
625 Python::attach(|py| {
626 let obj: Py<PyAny> = py.eval(cr"'\ud800'", None, None).unwrap().into();
627 assert!(obj
628 .bind(py)
629 .cast::<PyString>()
630 .unwrap()
631 .encode_utf8()
632 .is_err());
633 })
634 }
635
636 #[test]
637 fn test_to_string_lossy() {
638 Python::attach(|py| {
639 let py_string = py
640 .eval(cr"'🐈 Hello \ud800World'", None, None)
641 .unwrap()
642 .cast_into::<PyString>()
643 .unwrap();
644
645 assert_eq!(py_string.to_string_lossy(), "🐈 Hello ���World");
646 })
647 }
648
649 #[test]
650 fn test_debug_string() {
651 Python::attach(|py| {
652 let s = "Hello\n".into_pyobject(py).unwrap();
653 assert_eq!(format!("{s:?}"), "'Hello\\n'");
654 })
655 }
656
657 #[test]
658 fn test_display_string() {
659 Python::attach(|py| {
660 let s = "Hello\n".into_pyobject(py).unwrap();
661 assert_eq!(format!("{s}"), "Hello\n");
662 })
663 }
664
665 #[test]
666 fn test_string_from_encoded_object() {
667 Python::attach(|py| {
668 let py_bytes = PyBytes::new(py, b"ab\xFFcd");
669
670 let py_string = PyString::from_encoded_object(&py_bytes, None, None).unwrap_err();
672 assert!(py_string
673 .get_type(py)
674 .is(py.get_type::<crate::exceptions::PyUnicodeDecodeError>()));
675
676 let py_string =
678 PyString::from_encoded_object(&py_bytes, None, Some(c"ignore")).unwrap();
679
680 let result = py_string.to_cow().unwrap();
681 assert_eq!(result, "abcd");
682 });
683 }
684
685 #[test]
686 fn test_string_from_encoded_object_with_invalid_encoding_errors() {
687 Python::attach(|py| {
688 let py_bytes = PyBytes::new(py, b"abcd");
689
690 let err = PyString::from_encoded_object(&py_bytes, Some(c"wat"), None).unwrap_err();
692 assert!(err.is_instance(py, &py.get_type::<PyLookupError>()));
693 assert_eq!(err.to_string(), "LookupError: unknown encoding: wat");
694
695 let err =
697 PyString::from_encoded_object(&PyBytes::new(py, b"ab\xFFcd"), None, Some(c"wat"))
698 .unwrap_err();
699 assert!(err.is_instance(py, &py.get_type::<PyLookupError>()));
700 assert_eq!(
701 err.to_string(),
702 "LookupError: unknown error handler name 'wat'"
703 );
704 });
705 }
706
707 #[test]
708 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
709 fn test_string_data_ucs1() {
710 Python::attach(|py| {
711 let s = PyString::new(py, "hello, world");
712 let data = unsafe { s.data().unwrap() };
713
714 assert_eq!(data, PyStringData::Ucs1(b"hello, world"));
715 assert_eq!(data.to_string(py).unwrap(), Cow::Borrowed("hello, world"));
716 assert_eq!(data.to_string_lossy(), Cow::Borrowed("hello, world"));
717 })
718 }
719
720 #[test]
721 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
722 fn test_string_data_ucs1_invalid() {
723 Python::attach(|py| {
724 let buffer = b"f\xfe\0";
726 let ptr = unsafe {
727 crate::ffi::PyUnicode_FromKindAndData(
728 crate::ffi::PyUnicode_1BYTE_KIND as _,
729 buffer.as_ptr().cast(),
730 2,
731 )
732 };
733 assert!(!ptr.is_null());
734 let s = unsafe { ptr.assume_owned(py).cast_into_unchecked::<PyString>() };
735 let data = unsafe { s.data().unwrap() };
736 assert_eq!(data, PyStringData::Ucs1(b"f\xfe"));
737 let err = data.to_string(py).unwrap_err();
738 assert!(err.get_type(py).is(py.get_type::<PyUnicodeDecodeError>()));
739 assert!(err
740 .to_string()
741 .contains("'utf-8' codec can't decode byte 0xfe in position 1"));
742 assert_eq!(data.to_string_lossy(), Cow::Borrowed("f�"));
743 });
744 }
745
746 #[test]
747 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
748 fn test_string_data_ucs2() {
749 Python::attach(|py| {
750 let s = py.eval(c"'foo\\ud800'", None, None).unwrap();
751 let py_string = s.cast::<PyString>().unwrap();
752 let data = unsafe { py_string.data().unwrap() };
753
754 assert_eq!(data, PyStringData::Ucs2(&[102, 111, 111, 0xd800]));
755 assert_eq!(
756 data.to_string_lossy(),
757 Cow::Owned::<str>("foo�".to_string())
758 );
759 })
760 }
761
762 #[test]
763 #[cfg(all(not(any(Py_LIMITED_API, PyPy, GraalPy)), target_endian = "little"))]
764 fn test_string_data_ucs2_invalid() {
765 Python::attach(|py| {
766 let buffer = b"\x22\xff\x00\xd8\x00\x00";
768 let ptr = unsafe {
769 crate::ffi::PyUnicode_FromKindAndData(
770 crate::ffi::PyUnicode_2BYTE_KIND as _,
771 buffer.as_ptr().cast(),
772 2,
773 )
774 };
775 assert!(!ptr.is_null());
776 let s = unsafe { ptr.assume_owned(py).cast_into_unchecked::<PyString>() };
777 let data = unsafe { s.data().unwrap() };
778 assert_eq!(data, PyStringData::Ucs2(&[0xff22, 0xd800]));
779 let err = data.to_string(py).unwrap_err();
780 assert!(err.get_type(py).is(py.get_type::<PyUnicodeDecodeError>()));
781 assert!(err
782 .to_string()
783 .contains("'utf-16' codec can't decode bytes in position 0-3"));
784 assert_eq!(data.to_string_lossy(), Cow::Owned::<str>("B�".into()));
785 });
786 }
787
788 #[test]
789 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
790 fn test_string_data_ucs4() {
791 Python::attach(|py| {
792 let s = "哈哈🐈";
793 let py_string = PyString::new(py, s);
794 let data = unsafe { py_string.data().unwrap() };
795
796 assert_eq!(data, PyStringData::Ucs4(&[21704, 21704, 128008]));
797 assert_eq!(data.to_string_lossy(), Cow::Owned::<str>(s.to_string()));
798 })
799 }
800
801 #[test]
802 #[cfg(all(not(any(Py_LIMITED_API, PyPy, GraalPy)), target_endian = "little"))]
803 fn test_string_data_ucs4_invalid() {
804 Python::attach(|py| {
805 let buffer = b"\x00\x00\x02\x00\x00\xd8\x00\x00\x00\x00\x00\x00";
807 let ptr = unsafe {
808 crate::ffi::PyUnicode_FromKindAndData(
809 crate::ffi::PyUnicode_4BYTE_KIND as _,
810 buffer.as_ptr().cast(),
811 2,
812 )
813 };
814 assert!(!ptr.is_null());
815 let s = unsafe { ptr.assume_owned(py).cast_into_unchecked::<PyString>() };
816 let data = unsafe { s.data().unwrap() };
817 assert_eq!(data, PyStringData::Ucs4(&[0x20000, 0xd800]));
818 let err = data.to_string(py).unwrap_err();
819 assert!(err.get_type(py).is(py.get_type::<PyUnicodeDecodeError>()));
820 assert!(err
821 .to_string()
822 .contains("'utf-32' codec can't decode bytes in position 0-7"));
823 assert_eq!(data.to_string_lossy(), Cow::Owned::<str>("𠀀�".into()));
824 });
825 }
826
827 #[test]
828 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
829 fn test_pystring_from_bytes() {
830 Python::attach(|py| {
831 let result = PyString::from_bytes(py, "\u{2122}".as_bytes());
832 assert!(result.is_ok());
833 let result = PyString::from_bytes(py, b"\x80");
834 assert!(result
835 .unwrap_err()
836 .get_type(py)
837 .is(py.get_type::<PyUnicodeDecodeError>()));
838 });
839 }
840
841 #[test]
842 fn test_intern_string() {
843 Python::attach(|py| {
844 let py_string1 = PyString::intern(py, "foo");
845 assert_eq!(py_string1, "foo");
846
847 let py_string2 = PyString::intern(py, "foo");
848 assert_eq!(py_string2, "foo");
849
850 assert_eq!(py_string1.as_ptr(), py_string2.as_ptr());
851
852 let py_string3 = PyString::intern(py, "bar");
853 assert_eq!(py_string3, "bar");
854
855 assert_ne!(py_string1.as_ptr(), py_string3.as_ptr());
856 });
857 }
858
859 #[test]
860 fn test_py_to_str_utf8() {
861 Python::attach(|py| {
862 let s = "ascii 🐈";
863 let py_string = PyString::new(py, s).unbind();
864
865 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
866 assert_eq!(s, py_string.to_str(py).unwrap());
867
868 assert_eq!(s, py_string.to_cow(py).unwrap());
869 })
870 }
871
872 #[test]
873 fn test_py_to_str_surrogate() {
874 Python::attach(|py| {
875 let py_string: Py<PyString> = py
876 .eval(cr"'\ud800'", None, None)
877 .unwrap()
878 .extract()
879 .unwrap();
880
881 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
882 assert!(py_string.to_str(py).is_err());
883
884 assert!(py_string.to_cow(py).is_err());
885 })
886 }
887
888 #[test]
889 fn test_py_to_string_lossy() {
890 Python::attach(|py| {
891 let py_string: Py<PyString> = py
892 .eval(cr"'🐈 Hello \ud800World'", None, None)
893 .unwrap()
894 .extract()
895 .unwrap();
896 assert_eq!(py_string.to_string_lossy(py), "🐈 Hello ���World");
897 })
898 }
899
900 #[test]
901 fn test_comparisons() {
902 Python::attach(|py| {
903 let s = "hello, world";
904 let py_string = PyString::new(py, s);
905
906 assert_eq!(py_string, "hello, world");
907
908 assert_eq!(py_string, s);
909 assert_eq!(&py_string, s);
910 assert_eq!(s, py_string);
911 assert_eq!(s, &py_string);
912
913 assert_eq!(py_string, *s);
914 assert_eq!(&py_string, *s);
915 assert_eq!(*s, py_string);
916 assert_eq!(*s, &py_string);
917
918 let py_string = py_string.as_borrowed();
919
920 assert_eq!(py_string, s);
921 assert_eq!(&py_string, s);
922 assert_eq!(s, py_string);
923 assert_eq!(s, &py_string);
924
925 assert_eq!(py_string, *s);
926 assert_eq!(*s, py_string);
927 })
928 }
929}