diff --git a/newsfragments/4007.fixed.md b/newsfragments/4007.fixed.md new file mode 100644 index 00000000..ff905fb4 --- /dev/null +++ b/newsfragments/4007.fixed.md @@ -0,0 +1 @@ +Implement `Send` and `Sync` for `PyBackedStr` and `PyBackedBytes`. diff --git a/src/pybacked.rs b/src/pybacked.rs index dbb08652..01b9c2a6 100644 --- a/src/pybacked.rs +++ b/src/pybacked.rs @@ -16,14 +16,14 @@ use crate::{ pub struct PyBackedStr { #[allow(dead_code)] // only held so that the storage is not dropped storage: Py, - data: NonNull<[u8]>, + data: NonNull, } impl Deref for PyBackedStr { type Target = str; fn deref(&self) -> &str { - // Safety: `data` is known to be immutable utf8 string and owned by self - unsafe { std::str::from_utf8_unchecked(self.data.as_ref()) } + // Safety: `data` is known to be immutable and owned by self + unsafe { self.data.as_ref() } } } @@ -39,13 +39,18 @@ impl AsRef<[u8]> for PyBackedStr { } } +// Safety: the underlying Python str (or bytes) is immutable and +// safe to share between threads +unsafe impl Send for PyBackedStr {} +unsafe impl Sync for PyBackedStr {} + impl TryFrom> for PyBackedStr { type Error = PyErr; fn try_from(py_string: Bound<'_, PyString>) -> Result { #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] { let s = py_string.to_str()?; - let data = NonNull::from(s.as_bytes()); + let data = NonNull::from(s); Ok(Self { storage: py_string.as_any().to_owned().unbind(), data, @@ -54,8 +59,8 @@ impl TryFrom> for PyBackedStr { #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))] { let bytes = py_string.encode_utf8()?; - let b = bytes.as_bytes(); - let data = NonNull::from(b); + let s = unsafe { std::str::from_utf8_unchecked(bytes.as_bytes()) }; + let data = NonNull::from(s); Ok(Self { storage: bytes.into_any().unbind(), data, @@ -100,6 +105,11 @@ impl AsRef<[u8]> for PyBackedBytes { } } +// Safety: the underlying Python bytes or Rust bytes is immutable and +// safe to share between threads +unsafe impl Send for PyBackedBytes {} +unsafe impl Sync for PyBackedBytes {} + impl From> for PyBackedBytes { fn from(py_bytes: Bound<'_, PyBytes>) -> Self { let b = py_bytes.as_bytes(); @@ -201,4 +211,16 @@ mod test { assert_eq!(&*py_backed_bytes, b"abcde"); }); } + + #[test] + fn test_backed_types_send_sync() { + fn is_send() {} + fn is_sync() {} + + is_send::(); + is_sync::(); + + is_send::(); + is_sync::(); + } }