Skip to content

Commit f7fd609

Browse files
authored
add more conveniences to PyBackedStr (#5723)
* add more conveniences to `PyBackedStr` * newsfragments
1 parent 472ecb1 commit f7fd609

File tree

3 files changed

+82
-4
lines changed

3 files changed

+82
-4
lines changed

newsfragments/5723.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `PyBackedStr::as_str` and `PyBackedStr::as_py_str` methods.

newsfragments/5723.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `#[inline]` hints to many methods on `PyBackedStr`.

src/pybacked.rs

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

3858
impl 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

4666
impl AsRef<str> for PyBackedStr {
67+
#[inline]
4768
fn as_ref(&self) -> &str {
4869
self
4970
}
5071
}
5172

5273
impl 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
6089
unsafe impl Send for PyBackedStr {}
6190
unsafe impl Sync for PyBackedStr {}
6291

6392
impl 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 {
305337
macro_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

Comments
 (0)