| 1 | use crate::ffi_ptr_ext::FfiPtrExt; |
| 2 | use crate::py_result_ext::PyResultExt; |
| 3 | use crate::{ffi, PyAny}; |
| 4 | use crate::{Bound, Python}; |
| 5 | use crate::{PyErr, PyResult}; |
| 6 | use std::ffi::{CStr, CString}; |
| 7 | use std::os::raw::{c_char, c_int, c_void}; |
| 8 | /// Represents a Python Capsule |
| 9 | /// as described in [Capsules](https://docs.python.org/3/c-api/capsule.html#capsules): |
| 10 | /// > This subtype of PyObject represents an opaque value, useful for C extension |
| 11 | /// > modules who need to pass an opaque value (as a void* pointer) through Python |
| 12 | /// > code to other C code. It is often used to make a C function pointer defined |
| 13 | /// > in one module available to other modules, so the regular import mechanism can |
| 14 | /// > be used to access C APIs defined in dynamically loaded modules. |
| 15 | /// |
| 16 | /// Values of this type are accessed via PyO3's smart pointers, e.g. as |
| 17 | /// [`Py<PyCapsule>`][crate::Py] or [`Bound<'py, PyCapsule>`][Bound]. |
| 18 | /// |
| 19 | /// For APIs available on capsule objects, see the [`PyCapsuleMethods`] trait which is implemented for |
| 20 | /// [`Bound<'py, PyCapsule>`][Bound]. |
| 21 | /// |
| 22 | /// # Example |
| 23 | /// ``` |
| 24 | /// use pyo3::{prelude::*, types::PyCapsule}; |
| 25 | /// use std::ffi::CString; |
| 26 | /// |
| 27 | /// #[repr(C)] |
| 28 | /// struct Foo { |
| 29 | /// pub val: u32, |
| 30 | /// } |
| 31 | /// |
| 32 | /// let r = Python::with_gil(|py| -> PyResult<()> { |
| 33 | /// let foo = Foo { val: 123 }; |
| 34 | /// let name = CString::new("builtins.capsule" ).unwrap(); |
| 35 | /// |
| 36 | /// let capsule = PyCapsule::new(py, foo, Some(name.clone()))?; |
| 37 | /// |
| 38 | /// let module = PyModule::import(py, "builtins" )?; |
| 39 | /// module.add("capsule" , capsule)?; |
| 40 | /// |
| 41 | /// let cap: &Foo = unsafe { PyCapsule::import(py, name.as_ref())? }; |
| 42 | /// assert_eq!(cap.val, 123); |
| 43 | /// Ok(()) |
| 44 | /// }); |
| 45 | /// assert!(r.is_ok()); |
| 46 | /// ``` |
| 47 | #[repr (transparent)] |
| 48 | pub struct PyCapsule(PyAny); |
| 49 | |
| 50 | pyobject_native_type_core!(PyCapsule, pyobject_native_static_type_object!(ffi::PyCapsule_Type), #checkfunction=ffi::PyCapsule_CheckExact); |
| 51 | |
| 52 | impl PyCapsule { |
| 53 | /// Constructs a new capsule whose contents are `value`, associated with `name`. |
| 54 | /// `name` is the identifier for the capsule; if it is stored as an attribute of a module, |
| 55 | /// the name should be in the format `"modulename.attribute"`. |
| 56 | /// |
| 57 | /// It is checked at compile time that the type T is not zero-sized. Rust function items |
| 58 | /// need to be cast to a function pointer (`fn(args) -> result`) to be put into a capsule. |
| 59 | /// |
| 60 | /// # Example |
| 61 | /// |
| 62 | /// ``` |
| 63 | /// use pyo3::{prelude::*, types::PyCapsule}; |
| 64 | /// use std::ffi::CString; |
| 65 | /// |
| 66 | /// Python::with_gil(|py| { |
| 67 | /// let name = CString::new("foo" ).unwrap(); |
| 68 | /// let capsule = PyCapsule::new(py, 123_u32, Some(name)).unwrap(); |
| 69 | /// let val = unsafe { capsule.reference::<u32>() }; |
| 70 | /// assert_eq!(*val, 123); |
| 71 | /// }); |
| 72 | /// ``` |
| 73 | /// |
| 74 | /// However, attempting to construct a `PyCapsule` with a zero-sized type will not compile: |
| 75 | /// |
| 76 | /// ```compile_fail |
| 77 | /// use pyo3::{prelude::*, types::PyCapsule}; |
| 78 | /// use std::ffi::CString; |
| 79 | /// |
| 80 | /// Python::with_gil(|py| { |
| 81 | /// let capsule = PyCapsule::new(py, (), None).unwrap(); // Oops! `()` is zero sized! |
| 82 | /// }); |
| 83 | /// ``` |
| 84 | 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 name for [`PyCapsule::new`]. |
| 93 | #[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 | /// Constructs a new capsule whose contents are `value`, associated with `name`. |
| 104 | /// |
| 105 | /// Also provides a destructor: when the `PyCapsule` is destroyed, it will be passed the original object, |
| 106 | /// as well as a `*mut c_void` which will point to the capsule's context, if any. |
| 107 | /// |
| 108 | /// The `destructor` must be `Send`, because there is no guarantee which thread it will eventually |
| 109 | /// be called from. |
| 110 | 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 | // Sanity check for capsule layout |
| 122 | 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 name for [`PyCapsule::new_with_destructor`]. |
| 143 | #[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 | /// Imports an existing capsule. |
| 158 | /// |
| 159 | /// The `name` should match the path to the module attribute exactly in the form |
| 160 | /// of `"module.attribute"`, which should be the same as the name within the capsule. |
| 161 | /// |
| 162 | /// # Safety |
| 163 | /// |
| 164 | /// It must be known that the capsule imported by `name` contains an item of type `T`. |
| 165 | pub unsafe fn import<'py, T>(py: Python<'py>, name: &CStr) -> PyResult<&'py T> { |
| 166 | let ptr = unsafe { ffi::PyCapsule_Import(name.as_ptr(), false as c_int) }; |
| 167 | if ptr.is_null() { |
| 168 | Err(PyErr::fetch(py)) |
| 169 | } else { |
| 170 | Ok(unsafe { &*ptr.cast::<T>() }) |
| 171 | } |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | /// Implementation of functionality for [`PyCapsule`]. |
| 176 | /// |
| 177 | /// These methods are defined for the `Bound<'py, PyCapsule>` smart pointer, so to use method call |
| 178 | /// syntax these methods are separated into a trait, because stable Rust does not yet support |
| 179 | /// `arbitrary_self_types`. |
| 180 | #[doc (alias = "PyCapsule" )] |
| 181 | pub trait PyCapsuleMethods<'py>: crate::sealed::Sealed { |
| 182 | /// Sets the context pointer in the capsule. |
| 183 | /// |
| 184 | /// Returns an error if this capsule is not valid. |
| 185 | /// |
| 186 | /// # Notes |
| 187 | /// |
| 188 | /// The context is treated much like the value of the capsule, but should likely act as |
| 189 | /// a place to store any state management when using the capsule. |
| 190 | /// |
| 191 | /// If you want to store a Rust value as the context, and drop it from the destructor, use |
| 192 | /// `Box::into_raw` to convert it into a pointer, see the example. |
| 193 | /// |
| 194 | /// # Example |
| 195 | /// |
| 196 | /// ``` |
| 197 | /// use std::os::raw::c_void; |
| 198 | /// use std::sync::mpsc::{channel, Sender}; |
| 199 | /// use pyo3::{prelude::*, types::PyCapsule}; |
| 200 | /// |
| 201 | /// let (tx, rx) = channel::<String>(); |
| 202 | /// |
| 203 | /// fn destructor(val: u32, context: *mut c_void) { |
| 204 | /// let ctx = unsafe { *Box::from_raw(context.cast::<Sender<String>>()) }; |
| 205 | /// ctx.send("Destructor called!" .to_string()).unwrap(); |
| 206 | /// } |
| 207 | /// |
| 208 | /// Python::with_gil(|py| { |
| 209 | /// let capsule = |
| 210 | /// PyCapsule::new_with_destructor(py, 123, None, destructor as fn(u32, *mut c_void)) |
| 211 | /// .unwrap(); |
| 212 | /// let context = Box::new(tx); // `Sender<String>` is our context, box it up and ship it! |
| 213 | /// capsule.set_context(Box::into_raw(context).cast()).unwrap(); |
| 214 | /// // This scope will end, causing our destructor to be called... |
| 215 | /// }); |
| 216 | /// |
| 217 | /// assert_eq!(rx.recv(), Ok("Destructor called!" .to_string())); |
| 218 | /// ``` |
| 219 | fn set_context(&self, context: *mut c_void) -> PyResult<()>; |
| 220 | |
| 221 | /// Gets the current context stored in the capsule. If there is no context, the pointer |
| 222 | /// will be null. |
| 223 | /// |
| 224 | /// Returns an error if this capsule is not valid. |
| 225 | fn context(&self) -> PyResult<*mut c_void>; |
| 226 | |
| 227 | /// Obtains a reference to the value of this capsule. |
| 228 | /// |
| 229 | /// # Safety |
| 230 | /// |
| 231 | /// It must be known that this capsule is valid and its pointer is to an item of type `T`. |
| 232 | unsafe fn reference<T>(&self) -> &'py T; |
| 233 | |
| 234 | /// Gets the raw `c_void` pointer to the value in this capsule. |
| 235 | /// |
| 236 | /// Returns null if this capsule is not valid. |
| 237 | fn pointer(&self) -> *mut c_void; |
| 238 | |
| 239 | /// Checks if this is a valid capsule. |
| 240 | /// |
| 241 | /// Returns true if the stored `pointer()` is non-null. |
| 242 | fn is_valid(&self) -> bool; |
| 243 | |
| 244 | /// Retrieves the name of this capsule, if set. |
| 245 | /// |
| 246 | /// Returns an error if this capsule is not valid. |
| 247 | fn name(&self) -> PyResult<Option<&'py CStr>>; |
| 248 | } |
| 249 | |
| 250 | impl<'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 | unsafe { &*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 | // As well as if the stored pointer is null, PyCapsule_IsValid also returns false if |
| 285 | // self.as_ptr() is null or not a ptr to a PyCapsule object. Both of these are guaranteed |
| 286 | // to not be the case thanks to invariants of this PyCapsule struct. |
| 287 | 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 | // C layout, as PyCapsule::get_reference depends on `T` being first. |
| 305 | #[repr (C)] |
| 306 | struct CapsuleContents<T: 'static + Send, D: FnOnce(T, *mut c_void) + Send> { |
| 307 | /// Value of the capsule |
| 308 | value: T, |
| 309 | /// Destructor to be used by the capsule |
| 310 | destructor: D, |
| 311 | /// Name used when creating the capsule |
| 312 | name: Option<CString>, |
| 313 | } |
| 314 | |
| 315 | // Wrapping ffi::PyCapsule_Destructor for a user supplied FnOnce(T) for capsule destructor |
| 316 | unsafe extern "C" fn capsule_destructor<T: 'static + Send, F: FnOnce(T, *mut c_void) + Send>( |
| 317 | capsule: *mut ffi::PyObject, |
| 318 | ) { |
| 319 | unsafe { |
| 320 | let ptr: *mut c_void = ffi::PyCapsule_GetPointer(capsule, name:ffi::PyCapsule_GetName(capsule)); |
| 321 | let ctx: *mut c_void = ffi::PyCapsule_GetContext(capsule); |
| 322 | let CapsuleContents { |
| 323 | value: T, destructor: F, .. |
| 324 | } = *Box::from_raw(ptr.cast::<CapsuleContents<T, F>>()); |
| 325 | destructor(value, ctx) |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | /// Guarantee `T` is not zero sized at compile time. |
| 330 | // 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>` |
| 331 | #[doc (hidden)] |
| 332 | pub trait AssertNotZeroSized: Sized { |
| 333 | const _CONDITION: usize = (std::mem::size_of::<Self>() == 0) as usize; |
| 334 | const _CHECK: &'static str = |
| 335 | ["PyCapsule value type T must not be zero-sized!" ][Self::_CONDITION]; |
| 336 | #[allow (path_statements, clippy::no_effect)] |
| 337 | fn assert_not_zero_sized(&self) { |
| 338 | <Self as AssertNotZeroSized>::_CHECK; |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | impl<T> AssertNotZeroSized for T {} |
| 343 | |
| 344 | fn ensure_no_error(py: Python<'_>) -> PyResult<()> { |
| 345 | if let Some(err: PyErr) = PyErr::take(py) { |
| 346 | Err(err) |
| 347 | } else { |
| 348 | Ok(()) |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | fn name_ptr_ignore_error(slf: &Bound<'_, PyCapsule>) -> *const c_char { |
| 353 | let ptr: *const {unknown} = unsafe { ffi::PyCapsule_GetName(capsule:slf.as_ptr()) }; |
| 354 | if ptr.is_null() { |
| 355 | unsafe { ffi::PyErr_Clear() }; |
| 356 | } |
| 357 | ptr |
| 358 | } |
| 359 | |
| 360 | #[cfg (test)] |
| 361 | mod tests { |
| 362 | use crate::prelude::PyModule; |
| 363 | use crate::types::capsule::PyCapsuleMethods; |
| 364 | use crate::types::module::PyModuleMethods; |
| 365 | use crate::{types::PyCapsule, Py, PyResult, Python}; |
| 366 | use std::ffi::CString; |
| 367 | use std::os::raw::c_void; |
| 368 | use std::sync::mpsc::{channel, Sender}; |
| 369 | |
| 370 | #[test ] |
| 371 | fn test_pycapsule_struct() -> PyResult<()> { |
| 372 | #[repr (C)] |
| 373 | struct Foo { |
| 374 | pub val: u32, |
| 375 | } |
| 376 | |
| 377 | impl Foo { |
| 378 | fn get_val(&self) -> u32 { |
| 379 | self.val |
| 380 | } |
| 381 | } |
| 382 | |
| 383 | Python::with_gil(|py| -> PyResult<()> { |
| 384 | let foo = Foo { val: 123 }; |
| 385 | let name = CString::new("foo" ).unwrap(); |
| 386 | |
| 387 | let cap = PyCapsule::new(py, foo, Some(name.clone()))?; |
| 388 | assert!(cap.is_valid()); |
| 389 | |
| 390 | let foo_capi = unsafe { cap.reference::<Foo>() }; |
| 391 | assert_eq!(foo_capi.val, 123); |
| 392 | assert_eq!(foo_capi.get_val(), 123); |
| 393 | assert_eq!(cap.name().unwrap(), Some(name.as_ref())); |
| 394 | Ok(()) |
| 395 | }) |
| 396 | } |
| 397 | |
| 398 | #[test ] |
| 399 | fn test_pycapsule_func() { |
| 400 | fn foo(x: u32) -> u32 { |
| 401 | x |
| 402 | } |
| 403 | |
| 404 | let cap: Py<PyCapsule> = Python::with_gil(|py| { |
| 405 | let name = CString::new("foo" ).unwrap(); |
| 406 | let cap = PyCapsule::new(py, foo as fn(u32) -> u32, Some(name)).unwrap(); |
| 407 | cap.into() |
| 408 | }); |
| 409 | |
| 410 | Python::with_gil(move |py| { |
| 411 | let f = unsafe { cap.bind(py).reference::<fn(u32) -> u32>() }; |
| 412 | assert_eq!(f(123), 123); |
| 413 | }); |
| 414 | } |
| 415 | |
| 416 | #[test ] |
| 417 | fn test_pycapsule_context() -> PyResult<()> { |
| 418 | Python::with_gil(|py| { |
| 419 | let name = CString::new("foo" ).unwrap(); |
| 420 | let cap = PyCapsule::new(py, 0, Some(name))?; |
| 421 | |
| 422 | let c = cap.context()?; |
| 423 | assert!(c.is_null()); |
| 424 | |
| 425 | let ctx = Box::new(123_u32); |
| 426 | cap.set_context(Box::into_raw(ctx).cast())?; |
| 427 | |
| 428 | let ctx_ptr: *mut c_void = cap.context()?; |
| 429 | let ctx = unsafe { *Box::from_raw(ctx_ptr.cast::<u32>()) }; |
| 430 | assert_eq!(ctx, 123); |
| 431 | Ok(()) |
| 432 | }) |
| 433 | } |
| 434 | |
| 435 | #[test ] |
| 436 | fn test_pycapsule_import() -> PyResult<()> { |
| 437 | #[repr (C)] |
| 438 | struct Foo { |
| 439 | pub val: u32, |
| 440 | } |
| 441 | |
| 442 | Python::with_gil(|py| -> PyResult<()> { |
| 443 | let foo = Foo { val: 123 }; |
| 444 | let name = CString::new("builtins.capsule" ).unwrap(); |
| 445 | |
| 446 | let capsule = PyCapsule::new(py, foo, Some(name.clone()))?; |
| 447 | |
| 448 | let module = PyModule::import(py, "builtins" )?; |
| 449 | module.add("capsule" , capsule)?; |
| 450 | |
| 451 | // check error when wrong named passed for capsule. |
| 452 | let wrong_name = CString::new("builtins.non_existant" ).unwrap(); |
| 453 | let result: PyResult<&Foo> = unsafe { PyCapsule::import(py, wrong_name.as_ref()) }; |
| 454 | assert!(result.is_err()); |
| 455 | |
| 456 | // corret name is okay. |
| 457 | let cap: &Foo = unsafe { PyCapsule::import(py, name.as_ref())? }; |
| 458 | assert_eq!(cap.val, 123); |
| 459 | Ok(()) |
| 460 | }) |
| 461 | } |
| 462 | |
| 463 | #[test ] |
| 464 | fn test_vec_storage() { |
| 465 | let cap: Py<PyCapsule> = Python::with_gil(|py| { |
| 466 | let name = CString::new("foo" ).unwrap(); |
| 467 | |
| 468 | let stuff: Vec<u8> = vec![1, 2, 3, 4]; |
| 469 | let cap = PyCapsule::new(py, stuff, Some(name)).unwrap(); |
| 470 | |
| 471 | cap.into() |
| 472 | }); |
| 473 | |
| 474 | Python::with_gil(move |py| { |
| 475 | let ctx: &Vec<u8> = unsafe { cap.bind(py).reference() }; |
| 476 | assert_eq!(ctx, &[1, 2, 3, 4]); |
| 477 | }) |
| 478 | } |
| 479 | |
| 480 | #[test ] |
| 481 | fn test_vec_context() { |
| 482 | let context: Vec<u8> = vec![1, 2, 3, 4]; |
| 483 | |
| 484 | let cap: Py<PyCapsule> = Python::with_gil(|py| { |
| 485 | let name = CString::new("foo" ).unwrap(); |
| 486 | let cap = PyCapsule::new(py, 0, Some(name)).unwrap(); |
| 487 | cap.set_context(Box::into_raw(Box::new(&context)).cast()) |
| 488 | .unwrap(); |
| 489 | |
| 490 | cap.into() |
| 491 | }); |
| 492 | |
| 493 | Python::with_gil(move |py| { |
| 494 | let ctx_ptr: *mut c_void = cap.bind(py).context().unwrap(); |
| 495 | let ctx = unsafe { *Box::from_raw(ctx_ptr.cast::<&Vec<u8>>()) }; |
| 496 | assert_eq!(ctx, &vec![1_u8, 2, 3, 4]); |
| 497 | }) |
| 498 | } |
| 499 | |
| 500 | #[test ] |
| 501 | fn test_pycapsule_destructor() { |
| 502 | let (tx, rx) = channel::<bool>(); |
| 503 | |
| 504 | fn destructor(_val: u32, ctx: *mut c_void) { |
| 505 | assert!(!ctx.is_null()); |
| 506 | let context = unsafe { *Box::from_raw(ctx.cast::<Sender<bool>>()) }; |
| 507 | context.send(true).unwrap(); |
| 508 | } |
| 509 | |
| 510 | Python::with_gil(move |py| { |
| 511 | let name = CString::new("foo" ).unwrap(); |
| 512 | let cap = PyCapsule::new_with_destructor(py, 0, Some(name), destructor).unwrap(); |
| 513 | cap.set_context(Box::into_raw(Box::new(tx)).cast()).unwrap(); |
| 514 | }); |
| 515 | |
| 516 | // the destructor was called. |
| 517 | assert_eq!(rx.recv(), Ok(true)); |
| 518 | } |
| 519 | |
| 520 | #[test ] |
| 521 | fn test_pycapsule_no_name() { |
| 522 | Python::with_gil(|py| { |
| 523 | let cap = PyCapsule::new(py, 0usize, None).unwrap(); |
| 524 | |
| 525 | assert_eq!(unsafe { cap.reference::<usize>() }, &0usize); |
| 526 | assert_eq!(cap.name().unwrap(), None); |
| 527 | assert_eq!(cap.context().unwrap(), std::ptr::null_mut()); |
| 528 | }); |
| 529 | } |
| 530 | } |
| 531 | |