pyo3/types/capsule.rs
1#![deny(clippy::undocumented_unsafe_blocks)]
2
3use crate::ffi_ptr_ext::FfiPtrExt;
4use crate::py_result_ext::PyResultExt;
5use crate::{ffi, PyAny};
6use crate::{Bound, Python};
7use crate::{PyErr, PyResult};
8use std::ffi::{c_char, c_int, c_void};
9use std::ffi::{CStr, CString};
10use std::mem::offset_of;
11use std::ptr::{self, NonNull};
12
13/// Represents a Python Capsule
14/// as described in [Capsules](https://docs.python.org/3/c-api/capsule.html#capsules):
15/// > This subtype of PyObject represents an opaque value, useful for C extension
16/// > modules who need to pass an opaque value (as a void* pointer) through Python
17/// > code to other C code. It is often used to make a C function pointer defined
18/// > in one module available to other modules, so the regular import mechanism can
19/// > be used to access C APIs defined in dynamically loaded modules.
20///
21/// Values of this type are accessed via PyO3's smart pointers, e.g. as
22/// [`Py<PyCapsule>`][crate::Py] or [`Bound<'py, PyCapsule>`][Bound].
23///
24/// For APIs available on capsule objects, see the [`PyCapsuleMethods`] trait which is implemented for
25/// [`Bound<'py, PyCapsule>`][Bound].
26///
27/// # Example
28/// ```
29/// use pyo3::{prelude::*, types::PyCapsule, ffi::c_str};
30///
31/// #[repr(C)]
32/// struct Foo {
33/// pub val: u32,
34/// }
35///
36/// let r = Python::attach(|py| -> PyResult<()> {
37/// let foo = Foo { val: 123 };
38/// let capsule = PyCapsule::new(py, foo, Some(c"builtins.capsule".to_owned()))?;
39///
40/// let module = PyModule::import(py, "builtins")?;
41/// module.add("capsule", capsule)?;
42///
43/// let cap: &Foo = unsafe { PyCapsule::import(py, c"builtins.capsule")? };
44/// assert_eq!(cap.val, 123);
45/// Ok(())
46/// });
47/// assert!(r.is_ok());
48/// ```
49#[repr(transparent)]
50pub struct PyCapsule(PyAny);
51
52pyobject_native_type_core!(PyCapsule, pyobject_native_static_type_object!(ffi::PyCapsule_Type), "types", "CapsuleType", #checkfunction=ffi::PyCapsule_CheckExact);
53
54impl PyCapsule {
55 /// Constructs a new capsule whose contents are `value`, associated with `name`.
56 /// `name` is the identifier for the capsule; if it is stored as an attribute of a module,
57 /// the name should be in the format `"modulename.attribute"`.
58 ///
59 /// It is checked at compile time that the type T is not zero-sized. Rust function items
60 /// need to be cast to a function pointer (`fn(args) -> result`) to be put into a capsule.
61 ///
62 /// # Example
63 ///
64 /// ```
65 /// use pyo3::{prelude::*, types::PyCapsule, ffi::c_str};
66 /// use std::ffi::CStr;
67 /// use std::ptr::NonNull;
68 ///
69 /// // this can be c"foo" on Rust 1.77+
70 /// const NAME: &CStr = c"foo";
71 ///
72 /// Python::attach(|py| {
73 /// let capsule = PyCapsule::new(py, 123_u32, Some(NAME.to_owned())).unwrap();
74 /// let val: NonNull<u32> = capsule.pointer_checked(Some(NAME)).unwrap().cast();
75 /// assert_eq!(unsafe { *val.as_ref() }, 123);
76 /// });
77 /// ```
78 ///
79 /// However, attempting to construct a `PyCapsule` with a zero-sized type will not compile:
80 ///
81 /// ```compile_fail
82 /// use pyo3::{prelude::*, types::PyCapsule};
83 ///
84 /// Python::attach(|py| {
85 /// let capsule = PyCapsule::new(py, (), None).unwrap(); // Oops! `()` is zero sized!
86 /// });
87 /// ```
88 pub fn new<T: 'static + Send + AssertNotZeroSized>(
89 py: Python<'_>,
90 value: T,
91 name: Option<CString>,
92 ) -> PyResult<Bound<'_, Self>> {
93 Self::new_with_destructor(py, value, name, |_, _| {})
94 }
95
96 /// Constructs a new capsule whose contents are `value`, associated with `name`.
97 ///
98 /// Also provides a destructor: when the `PyCapsule` is destroyed, it will be passed the original object,
99 /// as well as a `*mut c_void` which will point to the capsule's context, if any.
100 ///
101 /// The `destructor` must be `Send`, because there is no guarantee which thread it will eventually
102 /// be called from.
103 pub fn new_with_destructor<
104 T: 'static + Send + AssertNotZeroSized,
105 F: FnOnce(T, *mut c_void) + Send,
106 >(
107 py: Python<'_>,
108 value: T,
109 name: Option<CString>,
110 destructor: F,
111 ) -> PyResult<Bound<'_, Self>> {
112 AssertNotZeroSized::assert_not_zero_sized(&value);
113
114 // Sanity check for capsule layout
115 debug_assert_eq!(offset_of!(CapsuleContents::<T, F>, value), 0);
116
117 let name_ptr = name.as_ref().map_or(std::ptr::null(), |name| name.as_ptr());
118 let val = Box::into_raw(Box::new(CapsuleContents {
119 value,
120 destructor,
121 name,
122 }));
123
124 // SAFETY:
125 // - `val` is a non-null pointer to valid capsule data
126 // - `name_ptr` is either a valid C string or null
127 // - `destructor` will delete this data when called
128 // - thread is attached to the Python interpreter
129 // - `PyCapsule_New` returns a new reference or null on error
130 unsafe {
131 ffi::PyCapsule_New(val.cast(), name_ptr, Some(capsule_destructor::<T, F>))
132 .assume_owned_or_err(py)
133 .cast_into_unchecked()
134 }
135 }
136
137 /// Constructs a new capsule from a raw pointer.
138 ///
139 /// Unlike [`PyCapsule::new`], which stores a value and sets the capsule's pointer
140 /// to that value's address, this method uses the pointer directly. This is useful
141 /// for APIs that expect the capsule to hold a specific address (e.g., a function
142 /// pointer for FFI) rather than a pointer to owned data.
143 ///
144 /// The capsule's name should follow Python's naming convention:
145 /// `"module.attribute"` for capsules stored as module attributes.
146 ///
147 /// # Safety
148 ///
149 /// - The pointer must be valid for its intended use case.
150 /// - If the pointer refers to data, that data must outlive the capsule.
151 /// - No destructor is registered; use [`PyCapsule::new_with_pointer_and_destructor`]
152 /// if cleanup is needed.
153 ///
154 /// # Example
155 ///
156 /// ```
157 /// use pyo3::{prelude::*, types::PyCapsule};
158 /// use std::ffi::c_void;
159 /// use std::ptr::NonNull;
160 ///
161 /// extern "C" fn my_ffi_handler(_: *mut c_void) -> *mut c_void {
162 /// std::ptr::null_mut()
163 /// }
164 ///
165 /// Python::attach(|py| {
166 /// let ptr = NonNull::new(my_ffi_handler as *mut c_void).unwrap();
167 ///
168 /// // SAFETY: `ptr` is a valid function pointer
169 /// let capsule = unsafe {
170 /// PyCapsule::new_with_pointer(py, ptr, c"my_module.my_ffi_handler")
171 /// }.unwrap();
172 ///
173 /// let retrieved = capsule.pointer_checked(Some(c"my_module.my_ffi_handler")).unwrap();
174 /// assert_eq!(retrieved.as_ptr(), my_ffi_handler as *mut c_void);
175 /// });
176 /// ```
177 pub unsafe fn new_with_pointer<'py>(
178 py: Python<'py>,
179 pointer: NonNull<c_void>,
180 name: &'static CStr,
181 ) -> PyResult<Bound<'py, Self>> {
182 // SAFETY: Caller guarantees pointer validity; destructor is None.
183 unsafe { Self::new_with_pointer_and_destructor(py, pointer, name, None) }
184 }
185
186 /// Constructs a new capsule from a raw pointer with an optional destructor.
187 ///
188 /// This is the full-featured version of [`PyCapsule::new_with_pointer`], allowing
189 /// a destructor to be called when the capsule is garbage collected.
190 ///
191 /// Unlike [`PyCapsule::new_with_destructor`], the destructor here must be a raw
192 /// `extern "C"` function pointer, not a Rust closure. This is because there is
193 /// no internal storage for a closure—the capsule holds only the raw pointer you
194 /// provide.
195 ///
196 /// # Safety
197 ///
198 /// - The pointer must be valid for its intended use case.
199 /// - If the pointer refers to data, that data must remain valid for the capsule's
200 /// lifetime, or the destructor must clean it up.
201 /// - The destructor, if provided, must be safe to call from any thread.
202 /// - The destructor should not panic. Panics cannot unwind across the FFI
203 /// boundary into Python, so a panic will abort the process.
204 ///
205 /// # Example
206 ///
207 /// ```
208 /// use pyo3::{prelude::*, types::PyCapsule};
209 /// use std::ffi::c_void;
210 /// use std::ptr::NonNull;
211 ///
212 /// unsafe extern "C" fn free_data(capsule: *mut pyo3::ffi::PyObject) {
213 /// let ptr = pyo3::ffi::PyCapsule_GetPointer(capsule, c"my_module.data".as_ptr());
214 /// if !ptr.is_null() {
215 /// drop(Box::from_raw(ptr as *mut u32));
216 /// }
217 /// }
218 ///
219 /// Python::attach(|py| {
220 /// let data = Box::new(42u32);
221 /// let ptr = NonNull::new(Box::into_raw(data).cast::<c_void>()).unwrap();
222 ///
223 /// // SAFETY: `ptr` is valid; `free_data` will deallocate it
224 /// let capsule = unsafe {
225 /// PyCapsule::new_with_pointer_and_destructor(
226 /// py,
227 /// ptr,
228 /// c"my_module.data",
229 /// Some(free_data),
230 /// )
231 /// }.unwrap();
232 /// });
233 /// ```
234 pub unsafe fn new_with_pointer_and_destructor<'py>(
235 py: Python<'py>,
236 pointer: NonNull<c_void>,
237 name: &'static CStr,
238 destructor: Option<ffi::PyCapsule_Destructor>,
239 ) -> PyResult<Bound<'py, Self>> {
240 let name_ptr = name.as_ptr();
241
242 // SAFETY:
243 // - `pointer` is non-null (guaranteed by `NonNull`)
244 // - `name_ptr` points to a valid C string (guaranteed by `&'static CStr`)
245 // - `destructor` is either None or a valid function pointer (caller guarantees)
246 // - Thread is attached to the Python interpreter
247 unsafe {
248 ffi::PyCapsule_New(pointer.as_ptr(), name_ptr, destructor)
249 .assume_owned_or_err(py)
250 .cast_into_unchecked()
251 }
252 }
253
254 /// Imports an existing capsule.
255 ///
256 /// The `name` should match the path to the module attribute exactly in the form
257 /// of `"module.attribute"`, which should be the same as the name within the capsule.
258 ///
259 /// # Safety
260 ///
261 /// It must be known that the capsule imported by `name` contains an item of type `T`.
262 pub unsafe fn import<'py, T>(py: Python<'py>, name: &CStr) -> PyResult<&'py T> {
263 // SAFETY: `name` is a valid C string, thread is attached to the Python interpreter
264 let ptr = unsafe { ffi::PyCapsule_Import(name.as_ptr(), false as c_int) };
265 if ptr.is_null() {
266 Err(PyErr::fetch(py))
267 } else {
268 // SAFETY: caller has upheld the safety contract
269 Ok(unsafe { &*ptr.cast::<T>() })
270 }
271 }
272}
273
274/// Implementation of functionality for [`PyCapsule`].
275///
276/// These methods are defined for the `Bound<'py, PyCapsule>` smart pointer, so to use method call
277/// syntax these methods are separated into a trait, because stable Rust does not yet support
278/// `arbitrary_self_types`.
279///
280/// # Name checking
281///
282/// Capsules contain pointers to arbitrary data which is cast to a specific type at runtime. This is
283/// inherently quite dangerous, so Python allows capsules to be "named" to provide a hint as to
284/// what data is contained in the capsule. Although not a perfect solution, this is better than
285/// nothing.
286///
287/// The methods in this trait take the `name` as an `Option<&CStr>`, which is compared to the name
288/// stored in the capsule (with `None` being used to indicate the capsule has no name).
289#[doc(alias = "PyCapsule")]
290pub trait PyCapsuleMethods<'py>: crate::sealed::Sealed {
291 /// Sets the context pointer in the capsule.
292 ///
293 /// Returns an error if this capsule is not valid.
294 ///
295 /// # Notes
296 ///
297 /// The context is treated much like the value of the capsule, but should likely act as
298 /// a place to store any state management when using the capsule.
299 ///
300 /// If you want to store a Rust value as the context, and drop it from the destructor, use
301 /// `Box::into_raw` to convert it into a pointer, see the example.
302 ///
303 /// # Example
304 ///
305 /// ```
306 /// use std::ffi::c_void;
307 /// use std::sync::mpsc::{channel, Sender};
308 /// use pyo3::{prelude::*, types::PyCapsule};
309 ///
310 /// let (tx, rx) = channel::<String>();
311 ///
312 /// fn destructor(val: u32, context: *mut c_void) {
313 /// let ctx = unsafe { *Box::from_raw(context.cast::<Sender<String>>()) };
314 /// ctx.send("Destructor called!".to_string()).unwrap();
315 /// }
316 ///
317 /// Python::attach(|py| {
318 /// let capsule =
319 /// PyCapsule::new_with_destructor(py, 123, None, destructor as fn(u32, *mut c_void))
320 /// .unwrap();
321 /// let context = Box::new(tx); // `Sender<String>` is our context, box it up and ship it!
322 /// capsule.set_context(Box::into_raw(context).cast()).unwrap();
323 /// // This scope will end, causing our destructor to be called...
324 /// });
325 ///
326 /// assert_eq!(rx.recv(), Ok("Destructor called!".to_string()));
327 /// ```
328 fn set_context(&self, context: *mut c_void) -> PyResult<()>;
329
330 /// Gets the current context stored in the capsule. If there is no context, the pointer
331 /// will be null.
332 ///
333 /// Returns an error if this capsule is not valid.
334 fn context(&self) -> PyResult<*mut c_void>;
335
336 /// Obtains a reference dereferenced from the pointer of this capsule, without checking its name.
337 ///
338 /// Because this method encourages dereferencing the pointer for longer than necessary, it
339 /// is deprecated. Prefer to use [`pointer_checked()`][PyCapsuleMethods::pointer_checked]
340 /// and dereference the pointer only for as short a time as possible.
341 ///
342 /// # Safety
343 ///
344 /// This performs a dereference of the pointer returned from [`pointer()`][PyCapsuleMethods::pointer].
345 ///
346 /// See the safety notes on [`pointer_checked()`][PyCapsuleMethods::pointer_checked].
347 #[deprecated(since = "0.27.0", note = "to be removed, see `pointer_checked()`")]
348 unsafe fn reference<T>(&self) -> &T;
349
350 /// Gets the raw pointer stored in this capsule, without checking its name.
351 #[deprecated(since = "0.27.0", note = "use `pointer_checked()` instead")]
352 fn pointer(&self) -> *mut c_void;
353
354 /// Gets the raw pointer stored in this capsule.
355 ///
356 /// Returns an error if the capsule is not [valid][`PyCapsuleMethods::is_valid_checked`] with the given `name`.
357 ///
358 /// # Safety
359 ///
360 /// This function itself is not `unsafe`, but dereferencing the returned pointer to produce a reference
361 /// is very dangerous:
362 /// - The pointer will need to be [.cast()][NonNull::cast] to a concrete type before dereferencing.
363 /// As per [name checking](#name-checking), there is no way to statically guarantee this cast is
364 /// correct, the name is the best available hint to guard against accidental misuse.
365 /// - Arbitrary Python code can change the contents of the capsule, which may invalidate the
366 /// pointer. The pointer and the reference produced by dereferencing the pointer should both
367 /// be considered invalid after arbitrary Python code has run.
368 ///
369 /// Users should take care to cast to the correct type and consume the pointer for as little
370 /// duration as possible.
371 fn pointer_checked(&self, name: Option<&CStr>) -> PyResult<NonNull<c_void>>;
372
373 /// Checks if the capsule pointer is not null.
374 ///
375 /// This does not perform any check on the name of the capsule, which is the only mechanism
376 /// that Python provides to make sure that the pointer has the expected type. Prefer to use
377 /// [`is_valid_checked()`][Self::is_valid_checked()] instead.
378 #[deprecated(since = "0.27.0", note = "use `is_valid_checked()` instead")]
379 fn is_valid(&self) -> bool;
380
381 /// Checks that the capsule name matches `name` and that the pointer is not null.
382 fn is_valid_checked(&self, name: Option<&CStr>) -> bool;
383
384 /// Retrieves the name of this capsule, if set.
385 ///
386 /// Returns an error if this capsule is not valid.
387 ///
388 /// See [`CapsuleName`] for details of how to consume the return value.
389 fn name(&self) -> PyResult<Option<CapsuleName>>;
390}
391
392impl<'py> PyCapsuleMethods<'py> for Bound<'py, PyCapsule> {
393 #[allow(clippy::not_unsafe_ptr_arg_deref)]
394 fn set_context(&self, context: *mut c_void) -> PyResult<()> {
395 // SAFETY:
396 // - `self.as_ptr()` is a valid object pointer
397 // - `context` is user-provided
398 // - thread is attached to the Python interpreter
399 let result = unsafe { ffi::PyCapsule_SetContext(self.as_ptr(), context) };
400 if result != 0 {
401 Err(PyErr::fetch(self.py()))
402 } else {
403 Ok(())
404 }
405 }
406
407 fn context(&self) -> PyResult<*mut c_void> {
408 // SAFETY:
409 // - `self.as_ptr()` is a valid object pointer
410 // - thread is attached to the Python interpreter
411 let ctx = unsafe { ffi::PyCapsule_GetContext(self.as_ptr()) };
412 if ctx.is_null() {
413 ensure_no_error(self.py())?
414 }
415 Ok(ctx)
416 }
417
418 #[allow(deprecated)]
419 unsafe fn reference<T>(&self) -> &T {
420 // SAFETY:
421 // - caller has upheld the safety contract
422 // - thread is attached to the Python interpreter
423 unsafe { &*self.pointer().cast() }
424 }
425
426 fn pointer(&self) -> *mut c_void {
427 // SAFETY: arguments to `PyCapsule_GetPointer` are valid, errors are handled properly
428 unsafe {
429 let ptr = ffi::PyCapsule_GetPointer(self.as_ptr(), name_ptr_ignore_error(self));
430 if ptr.is_null() {
431 ffi::PyErr_Clear();
432 }
433 ptr
434 }
435 }
436
437 fn pointer_checked(&self, name: Option<&CStr>) -> PyResult<NonNull<c_void>> {
438 // SAFETY:
439 // - `self.as_ptr()` is a valid object pointer
440 // - `name_ptr` is either a valid C string or null
441 // - thread is attached to the Python interpreter
442 let ptr = unsafe { ffi::PyCapsule_GetPointer(self.as_ptr(), name_ptr(name)) };
443 NonNull::new(ptr).ok_or_else(|| PyErr::fetch(self.py()))
444 }
445
446 fn is_valid(&self) -> bool {
447 // SAFETY: As well as if the stored pointer is null, PyCapsule_IsValid also returns false if
448 // self.as_ptr() is null or not a ptr to a PyCapsule object. Both of these are guaranteed
449 // to not be the case thanks to invariants of this PyCapsule struct.
450 let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), name_ptr_ignore_error(self)) };
451 r != 0
452 }
453
454 fn is_valid_checked(&self, name: Option<&CStr>) -> bool {
455 // SAFETY:
456 // - `self.as_ptr()` is a valid object pointer
457 // - `name_ptr` is either a valid C string or null
458 // - thread is attached to the Python interpreter
459 let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), name_ptr(name)) };
460 r != 0
461 }
462
463 fn name(&self) -> PyResult<Option<CapsuleName>> {
464 // SAFETY:
465 // - `self.as_ptr()` is a valid object pointer
466 // - thread is attached to the Python interpreter
467 let name = unsafe { ffi::PyCapsule_GetName(self.as_ptr()) };
468
469 match NonNull::new(name.cast_mut()) {
470 Some(name) => Ok(Some(CapsuleName { ptr: name })),
471 None => {
472 ensure_no_error(self.py())?;
473 Ok(None)
474 }
475 }
476 }
477}
478
479/// The name given to a `capsule` object.
480///
481/// This is a thin wrapper around `*const c_char`, which can be accessed with the [`as_ptr`][Self::as_ptr]
482/// method. The [`as_cstr`][Self::as_cstr] method can be used as a convenience to access the name as a `&CStr`.
483///
484/// There is no guarantee that this capsule name pointer valid for any length of time, as arbitrary
485/// Python code may change the name of a capsule object (by reaching native code which calls
486/// [`PyCapsule_SetName`][ffi::PyCapsule_SetName]). See the safety notes on [`as_cstr`][Self::as_cstr].
487#[derive(Clone, Copy)]
488pub struct CapsuleName {
489 /// Pointer to the name c-string, known to be non-null.
490 ptr: NonNull<c_char>,
491}
492
493impl CapsuleName {
494 /// Returns the capsule name as a `&CStr`.
495 ///
496 /// Note: this method is a thin wrapper around [`CStr::from_ptr`] so (as of Rust 1.91) incurs a
497 /// length calculation on each call.
498 ///
499 /// # Safety
500 ///
501 /// There is no guarantee that the capsule name remains valid for any length of time, as arbitrary
502 /// Python code may change the name of the capsule. The caller should be aware of any conventions
503 /// of the capsule in question related to the lifetime of the name (many capsule names are
504 /// statically allocated, i.e. have the `'static` lifetime, but Python does not require this).
505 ///
506 /// The returned lifetime `'a` is not related to the lifetime of the capsule itself, and the caller is
507 /// responsible for using the `&CStr` for as short a time as possible.
508 pub unsafe fn as_cstr<'a>(self) -> &'a CStr {
509 // SAFETY: caller has upheld the safety contract
510 unsafe { CStr::from_ptr(self.as_ptr()) }
511 }
512
513 /// Returns the raw pointer to the capsule name.
514 pub fn as_ptr(self) -> *const c_char {
515 self.ptr.as_ptr().cast_const()
516 }
517}
518
519// C layout, as casting the capsule pointer to `T` depends on `T` being first.
520#[repr(C)]
521struct CapsuleContents<T: 'static + Send, D: FnOnce(T, *mut c_void) + Send> {
522 /// Value of the capsule
523 value: T,
524 /// Destructor to be used by the capsule
525 destructor: D,
526 /// Name used when creating the capsule
527 name: Option<CString>,
528}
529
530// Wrapping ffi::PyCapsule_Destructor for a user supplied FnOnce(T) for capsule destructor
531unsafe extern "C" fn capsule_destructor<T: 'static + Send, F: FnOnce(T, *mut c_void) + Send>(
532 capsule: *mut ffi::PyObject,
533) {
534 /// Gets the pointer and context from the capsule.
535 ///
536 /// # Safety
537 ///
538 /// - `capsule` must be a valid capsule object
539 unsafe fn get_pointer_ctx(capsule: *mut ffi::PyObject) -> (*mut c_void, *mut c_void) {
540 // SAFETY: `capsule` is known to be a borrowed reference to the capsule being destroyed
541 let name = unsafe { ffi::PyCapsule_GetName(capsule) };
542
543 // SAFETY:
544 // - `capsule` is known to be a borrowed reference to the capsule being destroyed
545 // - `name` is known to be the capsule's name
546 let ptr = unsafe { ffi::PyCapsule_GetPointer(capsule, name) };
547
548 // SAFETY:
549 // - `capsule` is known to be a borrowed reference to the capsule being destroyed
550 let ctx = unsafe { ffi::PyCapsule_GetContext(capsule) };
551
552 (ptr, ctx)
553 }
554
555 // SAFETY: `capsule` is known to be a valid capsule object
556 let (ptr, ctx) = unsafe { get_pointer_ctx(capsule) };
557
558 // SAFETY: `capsule` was knowingly constructed with a boxed `CapsuleContents<T, F>`
559 // and is now being destroyed, so we can move the data from the box.
560 let CapsuleContents::<T, F> {
561 value, destructor, ..
562 } = *unsafe { Box::from_raw(ptr.cast()) };
563
564 destructor(value, ctx);
565}
566
567/// Guarantee `T` is not zero sized at compile time.
568// credit: `<https://users.rust-lang.org/t/is-it-possible-to-assert-at-compile-time-that-foo-t-is-not-called-with-a-zst/67685>`
569#[doc(hidden)]
570pub trait AssertNotZeroSized: Sized {
571 const _CONDITION: usize = (std::mem::size_of::<Self>() == 0) as usize;
572 const _CHECK: &'static str =
573 ["PyCapsule value type T must not be zero-sized!"][Self::_CONDITION];
574 #[allow(path_statements, clippy::no_effect)]
575 fn assert_not_zero_sized(&self) {
576 <Self as AssertNotZeroSized>::_CHECK;
577 }
578}
579
580impl<T> AssertNotZeroSized for T {}
581
582fn ensure_no_error(py: Python<'_>) -> PyResult<()> {
583 if let Some(err) = PyErr::take(py) {
584 Err(err)
585 } else {
586 Ok(())
587 }
588}
589
590fn name_ptr_ignore_error(slf: &Bound<'_, PyCapsule>) -> *const c_char {
591 // SAFETY:
592 // - `slf` is known to be a valid capsule object
593 // - thread is attached to the Python interpreter
594 let ptr = unsafe { ffi::PyCapsule_GetName(slf.as_ptr()) };
595 if ptr.is_null() {
596 // SAFETY: thread is attached to the Python interpreter
597 unsafe { ffi::PyErr_Clear() };
598 }
599 ptr
600}
601
602fn name_ptr(name: Option<&CStr>) -> *const c_char {
603 match name {
604 Some(name) => name.as_ptr(),
605 None => ptr::null(),
606 }
607}
608
609#[cfg(test)]
610mod tests {
611 use crate::prelude::PyModule;
612 use crate::types::capsule::PyCapsuleMethods;
613 use crate::types::module::PyModuleMethods;
614 use crate::{types::PyCapsule, Py, PyResult, Python};
615 use std::ffi::{c_void, CStr};
616 use std::ptr::NonNull;
617 use std::sync::mpsc::{channel, Sender};
618
619 const NAME: &CStr = c"foo";
620
621 #[test]
622 fn test_pycapsule_struct() {
623 #[repr(C)]
624 struct Foo {
625 pub val: u32,
626 }
627
628 impl Foo {
629 fn get_val(&self) -> u32 {
630 self.val
631 }
632 }
633
634 Python::attach(|py| {
635 let foo = Foo { val: 123 };
636
637 let cap = PyCapsule::new(py, foo, Some(NAME.to_owned())).unwrap();
638 assert!(cap.is_valid_checked(Some(NAME)));
639
640 let foo_capi = cap.pointer_checked(Some(NAME)).unwrap().cast::<Foo>();
641 // SAFETY: `foo_capi` contains a `Foo` and will be valid for the duration of the assert
642 assert_eq!(unsafe { foo_capi.as_ref() }.val, 123);
643 // SAFETY: as above
644 assert_eq!(unsafe { foo_capi.as_ref() }.get_val(), 123);
645 assert_eq!(
646 // SAFETY: `cap.name()` has a non-null name
647 unsafe { CStr::from_ptr(cap.name().unwrap().unwrap().as_ptr()) },
648 NAME
649 );
650 // SAFETY: as above
651 assert_eq!(unsafe { cap.name().unwrap().unwrap().as_cstr() }, NAME)
652 })
653 }
654
655 #[test]
656 fn test_pycapsule_func() {
657 fn foo(x: u32) -> u32 {
658 x
659 }
660
661 let cap: Py<PyCapsule> = Python::attach(|py| {
662 let cap = PyCapsule::new(py, foo as fn(u32) -> u32, Some(NAME.to_owned())).unwrap();
663 cap.into()
664 });
665
666 Python::attach(move |py| {
667 let f = cap
668 .bind(py)
669 .pointer_checked(Some(NAME))
670 .unwrap()
671 .cast::<fn(u32) -> u32>();
672 // SAFETY: `f` contains a `fn(u32) -> u32` and will be valid for the duration of the assert
673 assert_eq!(unsafe { f.as_ref() }(123), 123);
674 });
675 }
676
677 #[test]
678 fn test_pycapsule_context() {
679 Python::attach(|py| {
680 let cap = PyCapsule::new(py, 0, Some(NAME.to_owned())).unwrap();
681
682 let c = cap.context().unwrap();
683 assert!(c.is_null());
684
685 let ctx = Box::new(123_u32);
686 cap.set_context(Box::into_raw(ctx).cast()).unwrap();
687
688 let ctx_ptr: *mut c_void = cap.context().unwrap();
689 // SAFETY: `ctx_ptr` contains a boxed `u32` which is being moved out of the capsule
690 let ctx = unsafe { *Box::from_raw(ctx_ptr.cast::<u32>()) };
691 assert_eq!(ctx, 123);
692 })
693 }
694
695 #[test]
696 fn test_pycapsule_import() {
697 #[repr(C)]
698 struct Foo {
699 pub val: u32,
700 }
701
702 Python::attach(|py| {
703 let foo = Foo { val: 123 };
704 let name = c"builtins.capsule";
705
706 let capsule = PyCapsule::new(py, foo, Some(name.to_owned())).unwrap();
707
708 let module = PyModule::import(py, "builtins").unwrap();
709 module.add("capsule", capsule).unwrap();
710
711 // check error when wrong named passed for capsule.
712 // SAFETY: this function will fail so the cast is never done
713 let result: PyResult<&Foo> = unsafe { PyCapsule::import(py, c"builtins.non_existent") };
714 assert!(result.is_err());
715
716 // correct name is okay.
717 // SAFETY: we know the capsule at `name` contains a `Foo`
718 let cap: &Foo = unsafe { PyCapsule::import(py, name) }.unwrap();
719 assert_eq!(cap.val, 123);
720 })
721 }
722
723 #[test]
724 fn test_vec_storage() {
725 let cap: Py<PyCapsule> = Python::attach(|py| {
726 let stuff: Vec<u8> = vec![1, 2, 3, 4];
727 let cap = PyCapsule::new(py, stuff, Some(NAME.to_owned())).unwrap();
728 cap.into()
729 });
730
731 Python::attach(move |py| {
732 let stuff = cap
733 .bind(py)
734 .pointer_checked(Some(NAME))
735 .unwrap()
736 .cast::<Vec<u8>>();
737 // SAFETY: `stuff` contains a `Vec<u8>` and will be valid for the duration of the assert
738 assert_eq!(unsafe { stuff.as_ref() }, &[1, 2, 3, 4]);
739 })
740 }
741
742 #[test]
743 fn test_vec_context() {
744 let context: Vec<u8> = vec![1, 2, 3, 4];
745
746 let cap: Py<PyCapsule> = Python::attach(|py| {
747 let cap = PyCapsule::new(py, 0, Some(NAME.to_owned())).unwrap();
748 cap.set_context(Box::into_raw(Box::new(&context)).cast())
749 .unwrap();
750
751 cap.into()
752 });
753
754 Python::attach(move |py| {
755 let ctx_ptr: *mut c_void = cap.bind(py).context().unwrap();
756 // SAFETY: `ctx_ptr` contains a boxed `&Vec<u8>` which is being moved out of the capsule
757 let ctx = unsafe { *Box::from_raw(ctx_ptr.cast::<&Vec<u8>>()) };
758 assert_eq!(ctx, &vec![1_u8, 2, 3, 4]);
759 })
760 }
761
762 #[test]
763 fn test_pycapsule_destructor() {
764 let (tx, rx) = channel::<bool>();
765
766 fn destructor(_val: u32, ctx: *mut c_void) {
767 assert!(!ctx.is_null());
768 // SAFETY: `ctx` is known to be a boxed `Sender<bool>` needing deletion
769 let context = unsafe { *Box::from_raw(ctx.cast::<Sender<bool>>()) };
770 context.send(true).unwrap();
771 }
772
773 Python::attach(move |py| {
774 let cap =
775 PyCapsule::new_with_destructor(py, 0, Some(NAME.to_owned()), destructor).unwrap();
776 cap.set_context(Box::into_raw(Box::new(tx)).cast()).unwrap();
777 });
778
779 // the destructor was called.
780 assert_eq!(rx.recv(), Ok(true));
781 }
782
783 #[test]
784 fn test_pycapsule_no_name() {
785 Python::attach(|py| {
786 let cap = PyCapsule::new(py, 0usize, None).unwrap();
787
788 assert_eq!(
789 // SAFETY: `cap` is known to contain a `usize`
790 unsafe { cap.pointer_checked(None).unwrap().cast::<usize>().as_ref() },
791 &0usize
792 );
793 assert!(cap.name().unwrap().is_none());
794 assert_eq!(cap.context().unwrap(), std::ptr::null_mut());
795 });
796 }
797
798 #[test]
799 fn test_pycapsule_new_with_pointer() {
800 extern "C" fn dummy_handler(_: *mut c_void) -> *mut c_void {
801 std::ptr::null_mut()
802 }
803
804 let fn_ptr =
805 NonNull::new(dummy_handler as *mut c_void).expect("function pointer is non-null");
806
807 Python::attach(|py| {
808 // SAFETY: `fn_ptr` is known to point to `dummy_handler`
809 let capsule =
810 unsafe { PyCapsule::new_with_pointer(py, fn_ptr, c"test.dummy_handler") }.unwrap();
811
812 let retrieved_ptr = capsule
813 .pointer_checked(Some(c"test.dummy_handler"))
814 .unwrap();
815 assert_eq!(retrieved_ptr.as_ptr(), fn_ptr.as_ptr());
816 });
817 }
818
819 #[test]
820 fn test_pycapsule_new_with_pointer_and_destructor() {
821 use std::sync::mpsc::{channel, TryRecvError};
822
823 let (tx, rx) = channel::<bool>();
824
825 unsafe extern "C" fn destructor_fn(capsule: *mut crate::ffi::PyObject) {
826 // SAFETY:
827 // - `capsule` is a valid capsule object being destroyed by Python
828 // - The context was set to a valid `Box<Sender<bool>>` below
829 unsafe {
830 let ctx = crate::ffi::PyCapsule_GetContext(capsule);
831 if !ctx.is_null() {
832 let sender: Box<Sender<bool>> = Box::from_raw(ctx.cast());
833 let _ = sender.send(true);
834 }
835 }
836 }
837
838 let dummy_ptr =
839 NonNull::new(0xDEADBEEF as *mut c_void).expect("function pointer is non-null");
840
841 Python::attach(|py| {
842 // SAFETY:
843 // - `dummy_ptr` is non-null (it's a made-up address for testing)
844 // - We're providing a valid destructor function
845 let capsule = unsafe {
846 PyCapsule::new_with_pointer_and_destructor(
847 py,
848 dummy_ptr,
849 c"test.destructor_capsule",
850 Some(destructor_fn),
851 )
852 }
853 .unwrap();
854
855 // Store the sender in the capsule's context
856 let sender_box = Box::new(tx);
857 capsule
858 .set_context(Box::into_raw(sender_box).cast())
859 .unwrap();
860
861 // The destructor hasn't fired yet
862 assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
863 });
864
865 // After Python::attach scope ends, the capsule should be destroyed
866 assert_eq!(rx.recv(), Ok(true));
867 }
868
869 #[test]
870 fn test_pycapsule_pointer_checked_wrong_name() {
871 Python::attach(|py| {
872 let cap = PyCapsule::new(py, 123u32, Some(c"correct.name".to_owned())).unwrap();
873
874 // Requesting with wrong name should fail
875 let result = cap.pointer_checked(Some(c"wrong.name"));
876 assert!(result.is_err());
877
878 // Requesting with None when capsule has a name should also fail
879 let result = cap.pointer_checked(None);
880 assert!(result.is_err());
881 });
882 }
883
884 #[test]
885 fn test_pycapsule_pointer_checked_none_vs_some() {
886 Python::attach(|py| {
887 // Capsule with no name
888 let cap_no_name = PyCapsule::new(py, 123u32, None).unwrap();
889
890 // Should succeed with None
891 assert!(cap_no_name.pointer_checked(None).is_ok());
892
893 // Should fail with Some(name)
894 let result = cap_no_name.pointer_checked(Some(c"some.name"));
895 assert!(result.is_err());
896 });
897 }
898
899 #[test]
900 fn test_pycapsule_is_valid_checked_wrong_name() {
901 Python::attach(|py| {
902 let cap = PyCapsule::new(py, 123u32, Some(c"correct.name".to_owned())).unwrap();
903
904 // Should be valid with correct name
905 assert!(cap.is_valid_checked(Some(c"correct.name")));
906
907 // Should be invalid with wrong name
908 assert!(!cap.is_valid_checked(Some(c"wrong.name")));
909
910 // Should be invalid with None when capsule has a name
911 assert!(!cap.is_valid_checked(None));
912 });
913 }
914
915 #[test]
916 fn test_pycapsule_is_valid_checked_no_name() {
917 Python::attach(|py| {
918 let cap = PyCapsule::new(py, 123u32, None).unwrap();
919
920 // Should be valid with None
921 assert!(cap.is_valid_checked(None));
922
923 // Should be invalid with any name
924 assert!(!cap.is_valid_checked(Some(c"any.name")));
925 });
926 }
927
928 #[test]
929 fn test_pycapsule_context_on_invalid_capsule() {
930 Python::attach(|py| {
931 let cap = PyCapsule::new(py, 123u32, Some(NAME.to_owned())).unwrap();
932
933 // Invalidate the capsule
934 // SAFETY: intentionally breaking the capsule for testing
935 unsafe {
936 crate::ffi::PyCapsule_SetPointer(cap.as_ptr(), std::ptr::null_mut());
937 }
938
939 // context() on invalid capsule should fail
940 let result = cap.context();
941 assert!(result.is_err());
942 });
943 }
944
945 #[test]
946 fn test_pycapsule_import_wrong_module() {
947 Python::attach(|py| {
948 // Try to import from a non-existent module
949 // SAFETY: we expect this to fail, no cast will occur
950 let result: PyResult<&u32> =
951 unsafe { PyCapsule::import(py, c"nonexistent_module.capsule") };
952 assert!(result.is_err());
953 });
954 }
955
956 #[test]
957 fn test_pycapsule_import_wrong_attribute() {
958 Python::attach(|py| {
959 // Create a capsule and register it
960 let cap = PyCapsule::new(py, 123u32, Some(c"builtins.test_cap".to_owned())).unwrap();
961 let module = crate::prelude::PyModule::import(py, "builtins").unwrap();
962 module.add("test_cap", cap).unwrap();
963
964 // Try to import with wrong attribute name
965 // SAFETY: we expect this to fail
966 let result: PyResult<&u32> =
967 unsafe { PyCapsule::import(py, c"builtins.wrong_attribute") };
968 assert!(result.is_err());
969 });
970 }
971}