Skip to content

Commit c202c93

Browse files
committed
use Py2Borrowed to make PyBytesMethods slightly nicer
1 parent c5dce01 commit c202c93

File tree

3 files changed

+148
-9
lines changed

3 files changed

+148
-9
lines changed

src/ffi_ptr_ext.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use crate::{ffi, instance::Py2, PyAny, PyResult, Python};
1+
use crate::{
2+
ffi,
3+
instance::{Py2, Py2Borrowed},
4+
PyAny, PyResult, Python,
5+
};
26

37
mod sealed {
48
use super::*;
@@ -13,6 +17,24 @@ use sealed::Sealed;
1317
pub(crate) trait FfiPtrExt: Sealed {
1418
unsafe fn assume_owned_or_err(self, py: Python<'_>) -> PyResult<Py2<'_, PyAny>>;
1519
unsafe fn assume_owned(self, py: Python<'_>) -> Py2<'_, PyAny>;
20+
21+
/// Assumes this pointer is borrowed from a parent object.
22+
///
23+
/// Warning: the lifetime `'a` is not bounded by the function arguments; the caller is
24+
/// responsible to ensure this is tied to some appropriate lifetime.
25+
unsafe fn assume_borrowed_or_err<'a>(
26+
self,
27+
py: Python<'_>,
28+
) -> PyResult<Py2Borrowed<'a, '_, PyAny>>;
29+
30+
/// Same as `assume_borrowed_or_err`, but doesn't fetch an error on NULL.
31+
unsafe fn assume_borrowed_or_opt<'a>(
32+
self,
33+
py: Python<'_>,
34+
) -> Option<Py2Borrowed<'a, '_, PyAny>>;
35+
36+
/// Same as `assume_borrowed_or_err`, but panics on NULL.
37+
unsafe fn assume_borrowed<'a>(self, py: Python<'_>) -> Py2Borrowed<'a, '_, PyAny>;
1638
}
1739

1840
impl FfiPtrExt for *mut ffi::PyObject {
@@ -25,4 +47,25 @@ impl FfiPtrExt for *mut ffi::PyObject {
2547
unsafe fn assume_owned(self, py: Python<'_>) -> Py2<'_, PyAny> {
2648
Py2::from_owned_ptr(py, self)
2749
}
50+
51+
#[inline]
52+
unsafe fn assume_borrowed_or_err<'a>(
53+
self,
54+
py: Python<'_>,
55+
) -> PyResult<Py2Borrowed<'a, '_, PyAny>> {
56+
Py2Borrowed::from_ptr_or_err(py, self)
57+
}
58+
59+
#[inline]
60+
unsafe fn assume_borrowed_or_opt<'a>(
61+
self,
62+
py: Python<'_>,
63+
) -> Option<Py2Borrowed<'a, '_, PyAny>> {
64+
Py2Borrowed::from_ptr_or_opt(py, self)
65+
}
66+
67+
#[inline]
68+
unsafe fn assume_borrowed<'a>(self, py: Python<'_>) -> Py2Borrowed<'a, '_, PyAny> {
69+
Py2Borrowed::from_ptr(py, self)
70+
}
2871
}

src/instance.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,96 @@ unsafe impl<T> AsPyPointer for Py2<'_, T> {
217217
}
218218
}
219219

220+
/// A borrowed equivalent to `Py2`.
221+
///
222+
/// The advantage of this over `&Py2` is that it avoids the need to have a pointer-to-pointer, as Py2
223+
/// is already a pointer to an `ffi::PyObject``.
224+
#[repr(transparent)]
225+
pub(crate) struct Py2Borrowed<'a, 'py, T>(
226+
NonNull<ffi::PyObject>,
227+
PhantomData<&'a Py<T>>,
228+
Python<'py>,
229+
);
230+
231+
impl<'a, 'py> Py2Borrowed<'a, 'py, PyAny> {
232+
/// # Safety
233+
/// This is similar to `std::slice::from_raw_parts`, the lifetime `'a` is completely defined by
234+
/// the caller and it's the caller's responsibility to ensure that the reference this is
235+
/// derived from is valid for the lifetime `'a`.
236+
pub(crate) unsafe fn from_ptr_or_err(
237+
py: Python<'py>,
238+
ptr: *mut ffi::PyObject,
239+
) -> PyResult<Self> {
240+
NonNull::new(ptr).map_or_else(
241+
|| Err(PyErr::fetch(py)),
242+
|ptr| Ok(Self(ptr, PhantomData, py)),
243+
)
244+
}
245+
246+
/// # Safety
247+
/// This is similar to `std::slice::from_raw_parts`, the lifetime `'a` is completely defined by
248+
/// the caller and it's the caller's responsibility to ensure that the reference this is
249+
/// derived from is valid for the lifetime `'a`.
250+
pub(crate) unsafe fn from_ptr_or_opt(py: Python<'py>, ptr: *mut ffi::PyObject) -> Option<Self> {
251+
NonNull::new(ptr).map(|ptr| Self(ptr, PhantomData, py))
252+
}
253+
254+
/// # Safety
255+
/// This is similar to `std::slice::from_raw_parts`, the lifetime `'a` is completely defined by
256+
/// the caller and it's the caller's responsibility to ensure that the reference this is
257+
/// derived from is valid for the lifetime `'a`.
258+
pub(crate) unsafe fn from_ptr(py: Python<'py>, ptr: *mut ffi::PyObject) -> Self {
259+
Self(
260+
NonNull::new(ptr).unwrap_or_else(|| crate::err::panic_after_error(py)),
261+
PhantomData,
262+
py,
263+
)
264+
}
265+
}
266+
267+
impl<'a, 'py, T> From<&'a Py2<'py, T>> for Py2Borrowed<'a, 'py, T> {
268+
/// Create borrow on a Py2
269+
fn from(instance: &'a Py2<'py, T>) -> Self {
270+
Self(
271+
unsafe { NonNull::new_unchecked(instance.as_ptr()) },
272+
PhantomData,
273+
instance.py(),
274+
)
275+
}
276+
}
277+
278+
impl<'py, T> Py2Borrowed<'py, 'py, T>
279+
where
280+
T: HasPyGilRef,
281+
{
282+
pub(crate) fn from_gil_ref(gil_ref: &'py T::AsRefTarget) -> Self {
283+
// Safety: &'py T::AsRefTarget is expected to be a Python pointer,
284+
// so &'py T::AsRefTarget has the same layout as Self.
285+
unsafe { std::mem::transmute(gil_ref) }
286+
}
287+
288+
// pub(crate) fn into_gil_ref(self) -> &'py T::AsRefTarget {
289+
// // Safety: self is a borrow over `'py`.
290+
// unsafe { self.py().from_borrowed_ptr(self.0.as_ptr()) }
291+
// }
292+
}
293+
294+
impl<T> std::fmt::Debug for Py2Borrowed<'_, '_, T> {
295+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296+
Py2::fmt(self, f)
297+
}
298+
}
299+
300+
impl<'py, T> Deref for Py2Borrowed<'_, 'py, T> {
301+
type Target = Py2<'py, T>;
302+
303+
#[inline]
304+
fn deref(&self) -> &Py2<'py, T> {
305+
// safety: Py2 has the same layout as NonNull<ffi::PyObject>
306+
unsafe { &*(&self.0 as *const _ as *const Py2<'py, T>) }
307+
}
308+
}
309+
220310
/// A GIL-independent reference to an object allocated on the Python heap.
221311
///
222312
/// This type does not auto-dereference to the inner object because you must prove you hold the GIL to access it.
@@ -727,6 +817,10 @@ impl<T> Py<T> {
727817
Py2(py, ManuallyDrop::new(self))
728818
}
729819

820+
pub(crate) fn attach_borrow<'a, 'py>(&'a self, py: Python<'py>) -> Py2Borrowed<'a, 'py, T> {
821+
Py2Borrowed(self.0, PhantomData, py)
822+
}
823+
730824
/// Returns whether `self` and `other` point to the same object. To compare
731825
/// the equality of two objects (the `==` operator), use [`eq`](PyAny::eq).
732826
///

src/types/bytes.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::instance::Py2;
1+
use crate::instance::{Py2, Py2Borrowed};
22
use crate::{ffi, FromPyObject, IntoPy, Py, PyAny, PyResult, Python, ToPyObject};
33
use std::borrow::Cow;
44
use std::ops::Index;
@@ -89,9 +89,7 @@ impl PyBytes {
8989
/// Gets the Python string as a byte slice.
9090
#[inline]
9191
pub fn as_bytes(&self) -> &[u8] {
92-
let slice = Py2::borrowed_from_gil_ref(&self).as_bytes();
93-
// SAFETY: &self guarantees the reference is alive long enough
94-
unsafe { std::slice::from_raw_parts(slice.as_ptr(), slice.len()) }
92+
Py2Borrowed::from_gil_ref(self).as_bytes()
9593
}
9694
}
9795

@@ -109,6 +107,13 @@ pub(crate) trait PyBytesMethods<'py> {
109107
impl<'py> PyBytesMethods<'py> for Py2<'py, PyBytes> {
110108
#[inline]
111109
fn as_bytes(&self) -> &[u8] {
110+
Py2Borrowed::from(self).as_bytes()
111+
}
112+
}
113+
114+
impl<'a> Py2Borrowed<'a, '_, PyBytes> {
115+
/// Gets the Python string as a byte slice.
116+
fn as_bytes(self) -> &'a [u8] {
112117
unsafe {
113118
let buffer = ffi::PyBytes_AsString(self.as_ptr()) as *const u8;
114119
let length = ffi::PyBytes_Size(self.as_ptr()) as usize;
@@ -123,10 +128,7 @@ impl Py<PyBytes> {
123128
/// immutable, the result may be used for as long as the reference to
124129
/// `self` is held, including when the GIL is released.
125130
pub fn as_bytes<'a>(&'a self, py: Python<'_>) -> &'a [u8] {
126-
let slice = self.attach(py).as_bytes();
127-
// SAFETY: it is safe to access the immutable slice as long as the
128-
// reference to `self` is held.
129-
unsafe { std::slice::from_raw_parts(slice.as_ptr(), slice.len()) }
131+
self.attach_borrow(py).as_bytes()
130132
}
131133
}
132134

0 commit comments

Comments
 (0)