1use crate::ffi_ptr_ext::FfiPtrExt;
2use crate::py_result_ext::PyResultExt;
3use crate::{ffi, PyAny};
4use crate::{Bound, Python};
5use crate::{PyErr, PyResult};
6use std::ffi::{CStr, CString};
7use std::os::raw::{c_char, c_int, c_void};
8#[repr(transparent)]
48pub struct PyCapsule(PyAny);
49
50pyobject_native_type_core!(PyCapsule, pyobject_native_static_type_object!(ffi::PyCapsule_Type), #checkfunction=ffi::PyCapsule_CheckExact);
51
52impl PyCapsule {
53 pub fn new<T: 'static + Send + AssertNotZeroSized>(
85 py: Python<'_>,
86 value: T,
87 name: Option<CString>,
88 ) -> PyResult<Bound<'_, Self>> {
89 Self::new_with_destructor(py, value, name, |_, _| {})
90 }
91
92 #[deprecated(since = "0.23.0", note = "renamed to `PyCapsule::new`")]
94 #[inline]
95 pub fn new_bound<T: 'static + Send + AssertNotZeroSized>(
96 py: Python<'_>,
97 value: T,
98 name: Option<CString>,
99 ) -> PyResult<Bound<'_, Self>> {
100 Self::new(py, value, name)
101 }
102
103 pub fn new_with_destructor<
111 T: 'static + Send + AssertNotZeroSized,
112 F: FnOnce(T, *mut c_void) + Send,
113 >(
114 py: Python<'_>,
115 value: T,
116 name: Option<CString>,
117 destructor: F,
118 ) -> PyResult<Bound<'_, Self>> {
119 AssertNotZeroSized::assert_not_zero_sized(&value);
120
121 debug_assert_eq!(memoffset::offset_of!(CapsuleContents::<T, F>, value), 0);
123
124 let name_ptr = name.as_ref().map_or(std::ptr::null(), |name| name.as_ptr());
125 let val = Box::new(CapsuleContents {
126 value,
127 destructor,
128 name,
129 });
130
131 unsafe {
132 ffi::PyCapsule_New(
133 Box::into_raw(val).cast(),
134 name_ptr,
135 Some(capsule_destructor::<T, F>),
136 )
137 .assume_owned_or_err(py)
138 .downcast_into_unchecked()
139 }
140 }
141
142 #[deprecated(since = "0.23.0", note = "renamed to `PyCapsule::new_with_destructor`")]
144 #[inline]
145 pub fn new_bound_with_destructor<
146 T: 'static + Send + AssertNotZeroSized,
147 F: FnOnce(T, *mut c_void) + Send,
148 >(
149 py: Python<'_>,
150 value: T,
151 name: Option<CString>,
152 destructor: F,
153 ) -> PyResult<Bound<'_, Self>> {
154 Self::new_with_destructor(py, value, name, destructor)
155 }
156
157 pub unsafe fn import<'py, T>(py: Python<'py>, name: &CStr) -> PyResult<&'py T> {
166 let ptr = ffi::PyCapsule_Import(name.as_ptr(), false as c_int);
167 if ptr.is_null() {
168 Err(PyErr::fetch(py))
169 } else {
170 Ok(&*ptr.cast::<T>())
171 }
172 }
173}
174
175#[doc(alias = "PyCapsule")]
181pub trait PyCapsuleMethods<'py>: crate::sealed::Sealed {
182 fn set_context(&self, context: *mut c_void) -> PyResult<()>;
220
221 fn context(&self) -> PyResult<*mut c_void>;
226
227 unsafe fn reference<T>(&self) -> &'py T;
233
234 fn pointer(&self) -> *mut c_void;
238
239 fn is_valid(&self) -> bool;
243
244 fn name(&self) -> PyResult<Option<&'py CStr>>;
248}
249
250impl<'py> PyCapsuleMethods<'py> for Bound<'py, PyCapsule> {
251 #[allow(clippy::not_unsafe_ptr_arg_deref)]
252 fn set_context(&self, context: *mut c_void) -> PyResult<()> {
253 let result = unsafe { ffi::PyCapsule_SetContext(self.as_ptr(), context) };
254 if result != 0 {
255 Err(PyErr::fetch(self.py()))
256 } else {
257 Ok(())
258 }
259 }
260
261 fn context(&self) -> PyResult<*mut c_void> {
262 let ctx = unsafe { ffi::PyCapsule_GetContext(self.as_ptr()) };
263 if ctx.is_null() {
264 ensure_no_error(self.py())?
265 }
266 Ok(ctx)
267 }
268
269 unsafe fn reference<T>(&self) -> &'py T {
270 &*self.pointer().cast()
271 }
272
273 fn pointer(&self) -> *mut c_void {
274 unsafe {
275 let ptr = ffi::PyCapsule_GetPointer(self.as_ptr(), name_ptr_ignore_error(self));
276 if ptr.is_null() {
277 ffi::PyErr_Clear();
278 }
279 ptr
280 }
281 }
282
283 fn is_valid(&self) -> bool {
284 let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), name_ptr_ignore_error(self)) };
288 r != 0
289 }
290
291 fn name(&self) -> PyResult<Option<&'py CStr>> {
292 unsafe {
293 let ptr = ffi::PyCapsule_GetName(self.as_ptr());
294 if ptr.is_null() {
295 ensure_no_error(self.py())?;
296 Ok(None)
297 } else {
298 Ok(Some(CStr::from_ptr(ptr)))
299 }
300 }
301 }
302}
303
304#[repr(C)]
306struct CapsuleContents<T: 'static + Send, D: FnOnce(T, *mut c_void) + Send> {
307 value: T,
309 destructor: D,
311 name: Option<CString>,
313}
314
315unsafe extern "C" fn capsule_destructor<T: 'static + Send, F: FnOnce(T, *mut c_void) + Send>(
317 capsule: *mut ffi::PyObject,
318) {
319 let ptr = ffi::PyCapsule_GetPointer(capsule, ffi::PyCapsule_GetName(capsule));
320 let ctx = ffi::PyCapsule_GetContext(capsule);
321 let CapsuleContents {
322 value, destructor, ..
323 } = *Box::from_raw(ptr.cast::<CapsuleContents<T, F>>());
324 destructor(value, ctx)
325}
326
327#[doc(hidden)]
330pub trait AssertNotZeroSized: Sized {
331 const _CONDITION: usize = (std::mem::size_of::<Self>() == 0) as usize;
332 const _CHECK: &'static str =
333 ["PyCapsule value type T must not be zero-sized!"][Self::_CONDITION];
334 #[allow(path_statements, clippy::no_effect)]
335 fn assert_not_zero_sized(&self) {
336 <Self as AssertNotZeroSized>::_CHECK;
337 }
338}
339
340impl<T> AssertNotZeroSized for T {}
341
342fn ensure_no_error(py: Python<'_>) -> PyResult<()> {
343 if let Some(err) = PyErr::take(py) {
344 Err(err)
345 } else {
346 Ok(())
347 }
348}
349
350fn name_ptr_ignore_error(slf: &Bound<'_, PyCapsule>) -> *const c_char {
351 let ptr = unsafe { ffi::PyCapsule_GetName(slf.as_ptr()) };
352 if ptr.is_null() {
353 unsafe { ffi::PyErr_Clear() };
354 }
355 ptr
356}
357
358#[cfg(test)]
359mod tests {
360 use crate::prelude::PyModule;
361 use crate::types::capsule::PyCapsuleMethods;
362 use crate::types::module::PyModuleMethods;
363 use crate::{types::PyCapsule, Py, PyResult, Python};
364 use std::ffi::CString;
365 use std::os::raw::c_void;
366 use std::sync::mpsc::{channel, Sender};
367
368 #[test]
369 fn test_pycapsule_struct() -> PyResult<()> {
370 #[repr(C)]
371 struct Foo {
372 pub val: u32,
373 }
374
375 impl Foo {
376 fn get_val(&self) -> u32 {
377 self.val
378 }
379 }
380
381 Python::with_gil(|py| -> PyResult<()> {
382 let foo = Foo { val: 123 };
383 let name = CString::new("foo").unwrap();
384
385 let cap = PyCapsule::new(py, foo, Some(name.clone()))?;
386 assert!(cap.is_valid());
387
388 let foo_capi = unsafe { cap.reference::<Foo>() };
389 assert_eq!(foo_capi.val, 123);
390 assert_eq!(foo_capi.get_val(), 123);
391 assert_eq!(cap.name().unwrap(), Some(name.as_ref()));
392 Ok(())
393 })
394 }
395
396 #[test]
397 fn test_pycapsule_func() {
398 fn foo(x: u32) -> u32 {
399 x
400 }
401
402 let cap: Py<PyCapsule> = Python::with_gil(|py| {
403 let name = CString::new("foo").unwrap();
404 let cap = PyCapsule::new(py, foo as fn(u32) -> u32, Some(name)).unwrap();
405 cap.into()
406 });
407
408 Python::with_gil(move |py| {
409 let f = unsafe { cap.bind(py).reference::<fn(u32) -> u32>() };
410 assert_eq!(f(123), 123);
411 });
412 }
413
414 #[test]
415 fn test_pycapsule_context() -> PyResult<()> {
416 Python::with_gil(|py| {
417 let name = CString::new("foo").unwrap();
418 let cap = PyCapsule::new(py, 0, Some(name))?;
419
420 let c = cap.context()?;
421 assert!(c.is_null());
422
423 let ctx = Box::new(123_u32);
424 cap.set_context(Box::into_raw(ctx).cast())?;
425
426 let ctx_ptr: *mut c_void = cap.context()?;
427 let ctx = unsafe { *Box::from_raw(ctx_ptr.cast::<u32>()) };
428 assert_eq!(ctx, 123);
429 Ok(())
430 })
431 }
432
433 #[test]
434 fn test_pycapsule_import() -> PyResult<()> {
435 #[repr(C)]
436 struct Foo {
437 pub val: u32,
438 }
439
440 Python::with_gil(|py| -> PyResult<()> {
441 let foo = Foo { val: 123 };
442 let name = CString::new("builtins.capsule").unwrap();
443
444 let capsule = PyCapsule::new(py, foo, Some(name.clone()))?;
445
446 let module = PyModule::import(py, "builtins")?;
447 module.add("capsule", capsule)?;
448
449 let wrong_name = CString::new("builtins.non_existant").unwrap();
451 let result: PyResult<&Foo> = unsafe { PyCapsule::import(py, wrong_name.as_ref()) };
452 assert!(result.is_err());
453
454 let cap: &Foo = unsafe { PyCapsule::import(py, name.as_ref())? };
456 assert_eq!(cap.val, 123);
457 Ok(())
458 })
459 }
460
461 #[test]
462 fn test_vec_storage() {
463 let cap: Py<PyCapsule> = Python::with_gil(|py| {
464 let name = CString::new("foo").unwrap();
465
466 let stuff: Vec<u8> = vec![1, 2, 3, 4];
467 let cap = PyCapsule::new(py, stuff, Some(name)).unwrap();
468
469 cap.into()
470 });
471
472 Python::with_gil(move |py| {
473 let ctx: &Vec<u8> = unsafe { cap.bind(py).reference() };
474 assert_eq!(ctx, &[1, 2, 3, 4]);
475 })
476 }
477
478 #[test]
479 fn test_vec_context() {
480 let context: Vec<u8> = vec![1, 2, 3, 4];
481
482 let cap: Py<PyCapsule> = Python::with_gil(|py| {
483 let name = CString::new("foo").unwrap();
484 let cap = PyCapsule::new(py, 0, Some(name)).unwrap();
485 cap.set_context(Box::into_raw(Box::new(&context)).cast())
486 .unwrap();
487
488 cap.into()
489 });
490
491 Python::with_gil(move |py| {
492 let ctx_ptr: *mut c_void = cap.bind(py).context().unwrap();
493 let ctx = unsafe { *Box::from_raw(ctx_ptr.cast::<&Vec<u8>>()) };
494 assert_eq!(ctx, &vec![1_u8, 2, 3, 4]);
495 })
496 }
497
498 #[test]
499 fn test_pycapsule_destructor() {
500 let (tx, rx) = channel::<bool>();
501
502 fn destructor(_val: u32, ctx: *mut c_void) {
503 assert!(!ctx.is_null());
504 let context = unsafe { *Box::from_raw(ctx.cast::<Sender<bool>>()) };
505 context.send(true).unwrap();
506 }
507
508 Python::with_gil(move |py| {
509 let name = CString::new("foo").unwrap();
510 let cap = PyCapsule::new_with_destructor(py, 0, Some(name), destructor).unwrap();
511 cap.set_context(Box::into_raw(Box::new(tx)).cast()).unwrap();
512 });
513
514 assert_eq!(rx.recv(), Ok(true));
516 }
517
518 #[test]
519 fn test_pycapsule_no_name() {
520 Python::with_gil(|py| {
521 let cap = PyCapsule::new(py, 0usize, None).unwrap();
522
523 assert_eq!(unsafe { cap.reference::<usize>() }, &0usize);
524 assert_eq!(cap.name().unwrap(), None);
525 assert_eq!(cap.context().unwrap(), std::ptr::null_mut());
526 });
527 }
528}