@@ -9,9 +9,12 @@ use crate::{
99 } ,
1010 Borrowed , Bound , CastError , FromPyObject , IntoPyObject , Py , PyAny , PyErr , PyTypeInfo , Python ,
1111} ;
12- use std:: { convert:: Infallible , ops:: Deref , ptr:: NonNull , sync:: Arc } ;
12+ use std:: { borrow :: Borrow , convert:: Infallible , ops:: Deref , ptr:: NonNull , sync:: Arc } ;
1313
14- /// A wrapper around `str` where the storage is owned by a Python `bytes` or `str` object.
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.
1518///
1619/// This type gives access to the underlying data via a `Deref` implementation.
1720#[ cfg_attr( feature = "py-clone" , derive( Clone ) ) ]
@@ -27,40 +30,67 @@ impl PyBackedStr {
2730 /// Clones this by incrementing the reference count of the underlying Python object.
2831 ///
2932 /// Similar to [`Py::clone_ref`], this method is always available, even when the `py-clone` feature is disabled.
33+ #[ inline]
3034 pub fn clone_ref ( & self , py : Python < ' _ > ) -> Self {
3135 Self {
3236 storage : self . storage . clone_ref ( py) ,
3337 data : self . data ,
3438 }
3539 }
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+ }
3656}
3757
3858impl Deref for PyBackedStr {
3959 type Target = str ;
60+ #[ inline]
4061 fn deref ( & self ) -> & str {
41- // Safety: `data` is known to be immutable and owned by self
42- unsafe { self . data . as_ref ( ) }
62+ self . as_str ( )
4363 }
4464}
4565
4666impl AsRef < str > for PyBackedStr {
67+ #[ inline]
4768 fn as_ref ( & self ) -> & str {
4869 self
4970 }
5071}
5172
5273impl AsRef < [ u8 ] > for PyBackedStr {
74+ #[ inline]
5375 fn as_ref ( & self ) -> & [ u8 ] {
5476 self . as_bytes ( )
5577 }
5678}
5779
80+ impl Borrow < str > for PyBackedStr {
81+ #[ inline]
82+ fn borrow ( & self ) -> & str {
83+ self
84+ }
85+ }
86+
5887// Safety: the underlying Python str (or bytes) is immutable and
5988// safe to share between threads
6089unsafe impl Send for PyBackedStr { }
6190unsafe impl Sync for PyBackedStr { }
6291
6392impl std:: fmt:: Display for PyBackedStr {
93+ #[ inline]
6494 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
6595 self . deref ( ) . fmt ( f)
6696 }
@@ -99,6 +129,7 @@ impl FromPyObject<'_, '_> for PyBackedStr {
99129 #[ cfg( feature = "experimental-inspect" ) ]
100130 const INPUT_TYPE : TypeHint = PyString :: TYPE_HINT ;
101131
132+ #[ inline]
102133 fn extract ( obj : Borrowed < ' _ , ' _ , PyAny > ) -> Result < Self , Self :: Error > {
103134 let py_string = obj. cast :: < PyString > ( ) ?. to_owned ( ) ;
104135 Self :: try_from ( py_string)
@@ -114,6 +145,7 @@ impl<'py> IntoPyObject<'py> for PyBackedStr {
114145 const OUTPUT_TYPE : TypeHint = PyString :: TYPE_HINT ;
115146
116147 #[ cfg( any( Py_3_10 , not( Py_LIMITED_API ) ) ) ]
148+ #[ inline]
117149 fn into_pyobject ( self , py : Python < ' py > ) -> Result < Self :: Output , Self :: Error > {
118150 Ok ( self . storage . into_bound ( py) )
119151 }
@@ -305,36 +337,42 @@ impl<'py> IntoPyObject<'py> for &PyBackedBytes {
305337macro_rules! impl_traits {
306338 ( $slf: ty, $equiv: ty) => {
307339 impl std:: fmt:: Debug for $slf {
340+ #[ inline]
308341 fn fmt( & self , f: & mut std:: fmt:: Formatter <' _>) -> std:: fmt:: Result {
309342 self . deref( ) . fmt( f)
310343 }
311344 }
312345
313346 impl PartialEq for $slf {
347+ #[ inline]
314348 fn eq( & self , other: & Self ) -> bool {
315349 self . deref( ) == other. deref( )
316350 }
317351 }
318352
319353 impl PartialEq <$equiv> for $slf {
354+ #[ inline]
320355 fn eq( & self , other: & $equiv) -> bool {
321356 self . deref( ) == other
322357 }
323358 }
324359
325360 impl PartialEq <& $equiv> for $slf {
361+ #[ inline]
326362 fn eq( & self , other: &&$equiv) -> bool {
327363 self . deref( ) == * other
328364 }
329365 }
330366
331367 impl PartialEq <$slf> for $equiv {
368+ #[ inline]
332369 fn eq( & self , other: & $slf) -> bool {
333370 self == other. deref( )
334371 }
335372 }
336373
337374 impl PartialEq <$slf> for & $equiv {
375+ #[ inline]
338376 fn eq( & self , other: & $slf) -> bool {
339377 self == & other. deref( )
340378 }
@@ -343,30 +381,35 @@ macro_rules! impl_traits {
343381 impl Eq for $slf { }
344382
345383 impl PartialOrd for $slf {
384+ #[ inline]
346385 fn partial_cmp( & self , other: & Self ) -> Option <std:: cmp:: Ordering > {
347386 Some ( self . cmp( other) )
348387 }
349388 }
350389
351390 impl PartialOrd <$equiv> for $slf {
391+ #[ inline]
352392 fn partial_cmp( & self , other: & $equiv) -> Option <std:: cmp:: Ordering > {
353393 self . deref( ) . partial_cmp( other)
354394 }
355395 }
356396
357397 impl PartialOrd <$slf> for $equiv {
398+ #[ inline]
358399 fn partial_cmp( & self , other: & $slf) -> Option <std:: cmp:: Ordering > {
359400 self . partial_cmp( other. deref( ) )
360401 }
361402 }
362403
363404 impl Ord for $slf {
405+ #[ inline]
364406 fn cmp( & self , other: & Self ) -> std:: cmp:: Ordering {
365407 self . deref( ) . cmp( other. deref( ) )
366408 }
367409 }
368410
369411 impl std:: hash:: Hash for $slf {
412+ #[ inline]
370413 fn hash<H : std:: hash:: Hasher >( & self , state: & mut H ) {
371414 self . deref( ) . hash( state)
372415 }
@@ -571,6 +614,39 @@ mod test {
571614 } )
572615 }
573616
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+
574650 #[ cfg( feature = "py-clone" ) ]
575651 #[ test]
576652 fn test_backed_bytes_from_bytes_clone ( ) {
0 commit comments