diff --git a/newsfragments/5654.added.md b/newsfragments/5654.added.md new file mode 100644 index 00000000000..9c019f10fb0 --- /dev/null +++ b/newsfragments/5654.added.md @@ -0,0 +1 @@ +Add `PyBackedStr::clone_ref` and `PyBackedBytes::clone_ref` methods. diff --git a/src/pybacked.rs b/src/pybacked.rs index ba891c90012..ab4fe7351c0 100644 --- a/src/pybacked.rs +++ b/src/pybacked.rs @@ -16,14 +16,22 @@ use std::{convert::Infallible, ops::Deref, ptr::NonNull, sync::Arc}; /// This type gives access to the underlying data via a `Deref` implementation. #[cfg_attr(feature = "py-clone", derive(Clone))] pub struct PyBackedStr { - #[allow( - dead_code, - reason = "not read on Python 3.9 and older limited API, storage only on those versions" - )] storage: Py, data: NonNull, } +impl PyBackedStr { + /// Clones this by incrementing the reference count of the underlying Python object. + /// + /// Similar to [`Py::clone_ref`], this method is always available, even when the `py-clone` feature is disabled. + pub fn clone_ref(&self, py: Python<'_>) -> Self { + Self { + storage: self.storage.clone_ref(py), + data: self.data, + } + } +} + impl Deref for PyBackedStr { type Target = str; fn deref(&self) -> &str { @@ -147,6 +155,23 @@ enum PyBackedBytesStorage { Rust(Arc<[u8]>), } +impl PyBackedBytes { + /// Clones this by incrementing the reference count of the underlying data. + /// + /// Similar to [`Py::clone_ref`], this method is always available, even when the `py-clone` feature is disabled. + pub fn clone_ref(&self, py: Python<'_>) -> Self { + Self { + storage: match &self.storage { + PyBackedBytesStorage::Python(bytes) => { + PyBackedBytesStorage::Python(bytes.clone_ref(py)) + } + PyBackedBytesStorage::Rust(bytes) => PyBackedBytesStorage::Rust(bytes.clone()), + }, + data: self.data, + } + } +} + impl Deref for PyBackedBytes { type Target = [u8]; fn deref(&self) -> &[u8] { @@ -480,6 +505,19 @@ mod test { }); } + #[test] + fn test_backed_str_clone_ref() { + Python::attach(|py| { + let s1: PyBackedStr = PyString::new(py, "hello").try_into().unwrap(); + let s2 = s1.clone_ref(py); + assert_eq!(s1, s2); + assert!(s1.storage.is(&s2.storage)); + + drop(s1); + assert_eq!(s2, "hello"); + }); + } + #[test] fn test_backed_str_eq() { Python::attach(|py| { @@ -543,6 +581,24 @@ mod test { }); } + #[test] + fn test_backed_bytes_from_bytes_clone_ref() { + Python::attach(|py| { + let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into(); + let b2 = b1.clone_ref(py); + assert_eq!(b1, b2); + let (PyBackedBytesStorage::Python(s1), PyBackedBytesStorage::Python(s2)) = + (&b1.storage, &b2.storage) + else { + panic!("Expected Python-backed bytes"); + }; + assert!(s1.is(s2)); + + drop(b1); + assert_eq!(b2, b"abcde"); + }); + } + #[cfg(feature = "py-clone")] #[test] fn test_backed_bytes_from_bytearray_clone() { @@ -556,6 +612,24 @@ mod test { }); } + #[test] + fn test_backed_bytes_from_bytearray_clone_ref() { + Python::attach(|py| { + let b1: PyBackedBytes = PyByteArray::new(py, b"abcde").into(); + let b2 = b1.clone_ref(py); + assert_eq!(b1, b2); + let (PyBackedBytesStorage::Rust(s1), PyBackedBytesStorage::Rust(s2)) = + (&b1.storage, &b2.storage) + else { + panic!("Expected Rust-backed bytes"); + }; + assert!(Arc::ptr_eq(s1, s2)); + + drop(b1); + assert_eq!(b2, b"abcde"); + }); + } + #[test] fn test_backed_bytes_eq() { Python::attach(|py| {