Skip to content

Commit 3255558

Browse files
committed
Align the PyCapsule API with the original C API
1 parent 2dc118e commit 3255558

File tree

2 files changed

+74
-68
lines changed

2 files changed

+74
-68
lines changed

src/types/capsule.rs

Lines changed: 73 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::{Bound, Python};
55
use crate::{PyErr, PyResult};
66
use std::ffi::{CStr, CString};
77
use std::os::raw::{c_char, c_int, c_void};
8+
use std::ptr::{self, NonNull};
89
/// Represents a Python Capsule
910
/// as described in [Capsules](https://docs.python.org/3/c-api/capsule.html#capsules):
1011
/// > This subtype of PyObject represents an opaque value, useful for C extension
@@ -153,6 +154,32 @@ impl PyCapsule {
153154
/// `arbitrary_self_types`.
154155
#[doc(alias = "PyCapsule")]
155156
pub trait PyCapsuleMethods<'py>: crate::sealed::Sealed {
157+
/// Gets the raw pointer stored in the capsule.
158+
///
159+
/// Returns an error if the `name` does not exactly match the name stored in the capsule, or if
160+
/// the pointer stored in the capsule is null.
161+
fn pointer(&self, name: Option<&CStr>) -> PyResult<NonNull<c_void>>;
162+
163+
/// Obtains a reference dereferenced from the pointer of this capsule.
164+
///
165+
/// # Safety
166+
///
167+
/// It must be known that the pointer stored in the capsule points to an item of type `T`.
168+
unsafe fn reference<T>(&self, name: Option<&CStr>) -> PyResult<&'py T>;
169+
170+
/// Checks if this is a valid capsule.
171+
///
172+
/// Returns true if all of these conditions are satisfied:
173+
/// - `self` is a valid, non-null capsule (should be always true)
174+
/// - the stored pointer is not null
175+
/// - the name of the capsule is equal to `name`
176+
fn is_valid(&self, name: Option<&CStr>) -> bool;
177+
178+
/// Retrieves the name of this capsule, if set.
179+
///
180+
/// Returns an error if this capsule is not valid.
181+
fn name(&self) -> PyResult<Option<&'py CStr>>;
182+
156183
/// Sets the context pointer in the capsule.
157184
///
158185
/// Returns an error if this capsule is not valid.
@@ -197,68 +224,27 @@ pub trait PyCapsuleMethods<'py>: crate::sealed::Sealed {
197224
///
198225
/// Returns an error if this capsule is not valid.
199226
fn context(&self) -> PyResult<*mut c_void>;
200-
201-
/// Obtains a reference to the value of this capsule.
202-
///
203-
/// # Safety
204-
///
205-
/// It must be known that this capsule is valid and its pointer is to an item of type `T`.
206-
unsafe fn reference<T>(&self) -> &'py T;
207-
208-
/// Gets the raw `c_void` pointer to the value in this capsule.
209-
///
210-
/// Returns null if this capsule is not valid.
211-
fn pointer(&self) -> *mut c_void;
212-
213-
/// Checks if this is a valid capsule.
214-
///
215-
/// Returns true if the stored `pointer()` is non-null.
216-
fn is_valid(&self) -> bool;
217-
218-
/// Retrieves the name of this capsule, if set.
219-
///
220-
/// Returns an error if this capsule is not valid.
221-
fn name(&self) -> PyResult<Option<&'py CStr>>;
222227
}
223228

224229
impl<'py> PyCapsuleMethods<'py> for Bound<'py, PyCapsule> {
225-
#[allow(clippy::not_unsafe_ptr_arg_deref)]
226-
fn set_context(&self, context: *mut c_void) -> PyResult<()> {
227-
let result = unsafe { ffi::PyCapsule_SetContext(self.as_ptr(), context) };
228-
if result != 0 {
229-
Err(PyErr::fetch(self.py()))
230-
} else {
231-
Ok(())
230+
fn pointer(&self, name: Option<&CStr>) -> PyResult<NonNull<c_void>> {
231+
let ptr = unsafe { ffi::PyCapsule_GetPointer(self.as_ptr(), name_ptr(name)) };
232+
match NonNull::new(ptr) {
233+
Some(ptr) => Ok(ptr),
234+
None => Err(PyErr::fetch(self.py())),
232235
}
233236
}
234237

235-
fn context(&self) -> PyResult<*mut c_void> {
236-
let ctx = unsafe { ffi::PyCapsule_GetContext(self.as_ptr()) };
237-
if ctx.is_null() {
238-
ensure_no_error(self.py())?
239-
}
240-
Ok(ctx)
241-
}
242-
243-
unsafe fn reference<T>(&self) -> &'py T {
244-
unsafe { &*self.pointer().cast() }
245-
}
246-
247-
fn pointer(&self) -> *mut c_void {
248-
unsafe {
249-
let ptr = ffi::PyCapsule_GetPointer(self.as_ptr(), name_ptr_ignore_error(self));
250-
if ptr.is_null() {
251-
ffi::PyErr_Clear();
252-
}
253-
ptr
254-
}
238+
unsafe fn reference<T>(&self, name: Option<&CStr>) -> PyResult<&'py T> {
239+
let ptr = self.pointer(name)?;
240+
Ok(unsafe { &*ptr.as_ptr().cast() })
255241
}
256242

257-
fn is_valid(&self) -> bool {
243+
fn is_valid(&self, name: Option<&CStr>) -> bool {
258244
// As well as if the stored pointer is null, PyCapsule_IsValid also returns false if
259245
// self.as_ptr() is null or not a ptr to a PyCapsule object. Both of these are guaranteed
260246
// to not be the case thanks to invariants of this PyCapsule struct.
261-
let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), name_ptr_ignore_error(self)) };
247+
let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), name_ptr(name)) };
262248
r != 0
263249
}
264250

@@ -273,9 +259,27 @@ impl<'py> PyCapsuleMethods<'py> for Bound<'py, PyCapsule> {
273259
}
274260
}
275261
}
262+
263+
#[allow(clippy::not_unsafe_ptr_arg_deref)]
264+
fn set_context(&self, context: *mut c_void) -> PyResult<()> {
265+
let result = unsafe { ffi::PyCapsule_SetContext(self.as_ptr(), context) };
266+
if result != 0 {
267+
Err(PyErr::fetch(self.py()))
268+
} else {
269+
Ok(())
270+
}
271+
}
272+
273+
fn context(&self) -> PyResult<*mut c_void> {
274+
let ctx = unsafe { ffi::PyCapsule_GetContext(self.as_ptr()) };
275+
if ctx.is_null() {
276+
ensure_no_error(self.py())?
277+
}
278+
Ok(ctx)
279+
}
276280
}
277281

278-
// C layout, as PyCapsule::get_reference depends on `T` being first.
282+
// C layout, as PyCapsule::reference depends on `T` being first.
279283
#[repr(C)]
280284
struct CapsuleContents<T: 'static + Send, D: FnOnce(T, *mut c_void) + Send> {
281285
/// Value of the capsule
@@ -323,12 +327,11 @@ fn ensure_no_error(py: Python<'_>) -> PyResult<()> {
323327
}
324328
}
325329

326-
fn name_ptr_ignore_error(slf: &Bound<'_, PyCapsule>) -> *const c_char {
327-
let ptr = unsafe { ffi::PyCapsule_GetName(slf.as_ptr()) };
328-
if ptr.is_null() {
329-
unsafe { ffi::PyErr_Clear() };
330+
fn name_ptr(name: Option<&CStr>) -> *const c_char {
331+
match name {
332+
Some(name) => name.as_ptr(),
333+
None => ptr::null(),
330334
}
331-
ptr
332335
}
333336

334337
#[cfg(test)]
@@ -359,9 +362,9 @@ mod tests {
359362
let name = CString::new("foo").unwrap();
360363

361364
let cap = PyCapsule::new(py, foo, Some(name.clone()))?;
362-
assert!(cap.is_valid());
365+
assert!(cap.is_valid(Some(c"foo")));
363366

364-
let foo_capi = unsafe { cap.reference::<Foo>() };
367+
let foo_capi = unsafe { cap.reference::<Foo>(Some(name.as_ref())) }.unwrap();
365368
assert_eq!(foo_capi.val, 123);
366369
assert_eq!(foo_capi.get_val(), 123);
367370
assert_eq!(cap.name().unwrap(), Some(name.as_ref()));
@@ -375,14 +378,18 @@ mod tests {
375378
x
376379
}
377380

381+
let name = CString::new("foo").unwrap();
378382
let cap: Py<PyCapsule> = Python::attach(|py| {
379-
let name = CString::new("foo").unwrap();
380-
let cap = PyCapsule::new(py, foo as fn(u32) -> u32, Some(name)).unwrap();
383+
let cap = PyCapsule::new(py, foo as fn(u32) -> u32, Some(name.clone())).unwrap();
381384
cap.into()
382385
});
383386

384387
Python::attach(move |py| {
385-
let f = unsafe { cap.bind(py).reference::<fn(u32) -> u32>() };
388+
let f = unsafe {
389+
cap.bind(py)
390+
.reference::<fn(u32) -> u32>(Some(name.as_ref()))
391+
}
392+
.unwrap();
386393
assert_eq!(f(123), 123);
387394
});
388395
}
@@ -436,17 +443,16 @@ mod tests {
436443

437444
#[test]
438445
fn test_vec_storage() {
446+
let name = CString::new("foo").unwrap();
439447
let cap: Py<PyCapsule> = Python::attach(|py| {
440-
let name = CString::new("foo").unwrap();
441-
442448
let stuff: Vec<u8> = vec![1, 2, 3, 4];
443-
let cap = PyCapsule::new(py, stuff, Some(name)).unwrap();
449+
let cap = PyCapsule::new(py, stuff, Some(name.clone())).unwrap();
444450

445451
cap.into()
446452
});
447453

448454
Python::attach(move |py| {
449-
let ctx: &Vec<u8> = unsafe { cap.bind(py).reference() };
455+
let ctx: &Vec<u8> = unsafe { cap.bind(py).reference(Some(name.as_ref())) }.unwrap();
450456
assert_eq!(ctx, &[1, 2, 3, 4]);
451457
})
452458
}
@@ -496,7 +502,7 @@ mod tests {
496502
Python::attach(|py| {
497503
let cap = PyCapsule::new(py, 0usize, None).unwrap();
498504

499-
assert_eq!(unsafe { cap.reference::<usize>() }, &0usize);
505+
assert_eq!(unsafe { cap.reference::<usize>(None) }.unwrap(), &0usize);
500506
assert_eq!(cap.name().unwrap(), None);
501507
assert_eq!(cap.context().unwrap(), std::ptr::null_mut());
502508
});

src/types/function.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ impl PyCFunction {
9696
)?;
9797

9898
// Safety: just created the capsule with type ClosureDestructor<F> above
99-
let data = unsafe { capsule.reference::<ClosureDestructor<F>>() };
99+
let data = unsafe { capsule.reference::<ClosureDestructor<F>>(Some(name)) }?;
100100

101101
unsafe {
102102
ffi::PyCFunction_NewEx(data.def.get(), capsule.as_ptr(), std::ptr::null_mut())

0 commit comments

Comments
 (0)