diff --git a/src/prelude.rs b/src/prelude.rs index 06d283c9..47951aed 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -29,6 +29,7 @@ pub use crate::types::any::PyAnyMethods; pub use crate::types::boolobject::PyBoolMethods; pub use crate::types::bytearray::PyByteArrayMethods; pub use crate::types::bytes::PyBytesMethods; +pub use crate::types::capsule::PyCapsuleMethods; pub use crate::types::dict::PyDictMethods; pub use crate::types::float::PyFloatMethods; pub use crate::types::frozenset::PyFrozenSetMethods; diff --git a/src/types/capsule.rs b/src/types/capsule.rs index f97320eb..30e77391 100644 --- a/src/types/capsule.rs +++ b/src/types/capsule.rs @@ -1,6 +1,8 @@ -use crate::Python; -use crate::{ffi, PyAny}; +use crate::ffi_ptr_ext::FfiPtrExt; +use crate::py_result_ext::PyResultExt; +use crate::{ffi, PyAny, PyNativeType}; use crate::{pyobject_native_type_core, PyErr, PyResult}; +use crate::{Bound, Python}; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_int, c_void}; @@ -27,7 +29,7 @@ use std::os::raw::{c_char, c_int, c_void}; /// let foo = Foo { val: 123 }; /// let name = CString::new("builtins.capsule").unwrap(); /// -/// let capsule = PyCapsule::new(py, foo, Some(name.clone()))?; +/// let capsule = PyCapsule::new_bound(py, foo, Some(name.clone()))?; /// /// let module = PyModule::import(py, "builtins")?; /// module.add("capsule", capsule)?; @@ -44,6 +46,22 @@ pub struct PyCapsule(PyAny); pyobject_native_type_core!(PyCapsule, pyobject_native_static_type_object!(ffi::PyCapsule_Type), #checkfunction=ffi::PyCapsule_CheckExact); impl PyCapsule { + /// Deprecated form of [`PyCapsule::new_bound`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyCapsule::new` will be replaced by `PyCapsule::new_bound` in a future PyO3 version" + ) + )] + pub fn new( + py: Python<'_>, + value: T, + name: Option, + ) -> PyResult<&Self> { + Self::new_bound(py, value, name).map(Bound::into_gil_ref) + } + /// Constructs a new capsule whose contents are `value`, associated with `name`. /// `name` is the identifier for the capsule; if it is stored as an attribute of a module, /// the name should be in the format `"modulename.attribute"`. @@ -59,7 +77,7 @@ impl PyCapsule { /// /// Python::with_gil(|py| { /// let name = CString::new("foo").unwrap(); - /// let capsule = PyCapsule::new(py, 123_u32, Some(name)).unwrap(); + /// let capsule = PyCapsule::new_bound(py, 123_u32, Some(name)).unwrap(); /// let val = unsafe { capsule.reference::() }; /// assert_eq!(*val, 123); /// }); @@ -72,24 +90,25 @@ impl PyCapsule { /// use std::ffi::CString; /// /// Python::with_gil(|py| { - /// let capsule = PyCapsule::new(py, (), None).unwrap(); // Oops! `()` is zero sized! + /// let capsule = PyCapsule::new_bound(py, (), None).unwrap(); // Oops! `()` is zero sized! /// }); /// ``` - pub fn new( + pub fn new_bound( py: Python<'_>, value: T, name: Option, - ) -> PyResult<&Self> { - Self::new_with_destructor(py, value, name, |_, _| {}) + ) -> PyResult> { + Self::new_bound_with_destructor(py, value, name, |_, _| {}) } - /// Constructs a new capsule whose contents are `value`, associated with `name`. - /// - /// Also provides a destructor: when the `PyCapsule` is destroyed, it will be passed the original object, - /// as well as a `*mut c_void` which will point to the capsule's context, if any. - /// - /// The `destructor` must be `Send`, because there is no guarantee which thread it will eventually - /// be called from. + /// Deprecated form of [`PyCapsule::new_bound_with_destructor`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyCapsule::new_with_destructor` will be replaced by `PyCapsule::new_bound_with_destructor` in a future PyO3 version" + ) + )] pub fn new_with_destructor< T: 'static + Send + AssertNotZeroSized, F: FnOnce(T, *mut c_void) + Send, @@ -99,6 +118,25 @@ impl PyCapsule { name: Option, destructor: F, ) -> PyResult<&'_ Self> { + Self::new_bound_with_destructor(py, value, name, destructor).map(Bound::into_gil_ref) + } + + /// Constructs a new capsule whose contents are `value`, associated with `name`. + /// + /// Also provides a destructor: when the `PyCapsule` is destroyed, it will be passed the original object, + /// as well as a `*mut c_void` which will point to the capsule's context, if any. + /// + /// The `destructor` must be `Send`, because there is no guarantee which thread it will eventually + /// be called from. + pub fn new_bound_with_destructor< + T: 'static + Send + AssertNotZeroSized, + F: FnOnce(T, *mut c_void) + Send, + >( + py: Python<'_>, + value: T, + name: Option, + destructor: F, + ) -> PyResult> { AssertNotZeroSized::assert_not_zero_sized(&value); // Sanity check for capsule layout @@ -112,12 +150,13 @@ impl PyCapsule { }); unsafe { - let cap_ptr = ffi::PyCapsule_New( - Box::into_raw(val) as *mut c_void, + ffi::PyCapsule_New( + Box::into_raw(val).cast(), name_ptr, Some(capsule_destructor::), - ); - py.from_owned_ptr_or_err(cap_ptr) + ) + .assume_owned_or_err(py) + .downcast_into_unchecked() } } @@ -134,7 +173,7 @@ impl PyCapsule { if ptr.is_null() { Err(PyErr::fetch(py)) } else { - Ok(&*(ptr as *const T)) + Ok(&*ptr.cast::()) } } @@ -160,29 +199,23 @@ impl PyCapsule { /// let (tx, rx) = channel::(); /// /// fn destructor(val: u32, context: *mut c_void) { - /// let ctx = unsafe { *Box::from_raw(context as *mut Sender) }; + /// let ctx = unsafe { *Box::from_raw(context.cast::>()) }; /// ctx.send("Destructor called!".to_string()).unwrap(); /// } /// /// Python::with_gil(|py| { /// let capsule = - /// PyCapsule::new_with_destructor(py, 123, None, destructor as fn(u32, *mut c_void)) + /// PyCapsule::new_bound_with_destructor(py, 123, None, destructor as fn(u32, *mut c_void)) /// .unwrap(); /// let context = Box::new(tx); // `Sender` is our context, box it up and ship it! - /// capsule.set_context(Box::into_raw(context) as *mut c_void).unwrap(); + /// capsule.set_context(Box::into_raw(context).cast()).unwrap(); /// // This scope will end, causing our destructor to be called... /// }); /// /// assert_eq!(rx.recv(), Ok("Destructor called!".to_string())); /// ``` - #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn set_context(&self, context: *mut c_void) -> PyResult<()> { - let result = unsafe { ffi::PyCapsule_SetContext(self.as_ptr(), context) }; - if result != 0 { - Err(PyErr::fetch(self.py())) - } else { - Ok(()) - } + self.as_borrowed().set_context(context) } /// Gets the current context stored in the capsule. If there is no context, the pointer @@ -190,11 +223,7 @@ impl PyCapsule { /// /// Returns an error if this capsule is not valid. pub fn context(&self) -> PyResult<*mut c_void> { - let ctx = unsafe { ffi::PyCapsule_GetContext(self.as_ptr()) }; - if ctx.is_null() { - ensure_no_error(self.py())? - } - Ok(ctx) + self.as_borrowed().context() } /// Obtains a reference to the value of this capsule. @@ -203,15 +232,132 @@ impl PyCapsule { /// /// It must be known that this capsule is valid and its pointer is to an item of type `T`. pub unsafe fn reference(&self) -> &T { - &*(self.pointer() as *const T) + self.as_borrowed().reference() } /// Gets the raw `c_void` pointer to the value in this capsule. /// /// Returns null if this capsule is not valid. pub fn pointer(&self) -> *mut c_void { + self.as_borrowed().pointer() + } + + /// Checks if this is a valid capsule. + /// + /// Returns true if the stored `pointer()` is non-null. + pub fn is_valid(&self) -> bool { + self.as_borrowed().is_valid() + } + + /// Retrieves the name of this capsule, if set. + /// + /// Returns an error if this capsule is not valid. + pub fn name(&self) -> PyResult> { + self.as_borrowed().name() + } +} + +/// Implementation of functionality for [`PyCapsule`]. +/// +/// These methods are defined for the `Bound<'py, PyCapsule>` smart pointer, so to use method call +/// syntax these methods are separated into a trait, because stable Rust does not yet support +/// `arbitrary_self_types`. +#[doc(alias = "PyCapsule")] +pub trait PyCapsuleMethods<'py> { + /// Sets the context pointer in the capsule. + /// + /// Returns an error if this capsule is not valid. + /// + /// # Notes + /// + /// The context is treated much like the value of the capsule, but should likely act as + /// a place to store any state management when using the capsule. + /// + /// If you want to store a Rust value as the context, and drop it from the destructor, use + /// `Box::into_raw` to convert it into a pointer, see the example. + /// + /// # Example + /// + /// ``` + /// use std::sync::mpsc::{channel, Sender}; + /// use libc::c_void; + /// use pyo3::{prelude::*, types::PyCapsule}; + /// + /// let (tx, rx) = channel::(); + /// + /// fn destructor(val: u32, context: *mut c_void) { + /// let ctx = unsafe { *Box::from_raw(context.cast::>()) }; + /// ctx.send("Destructor called!".to_string()).unwrap(); + /// } + /// + /// Python::with_gil(|py| { + /// let capsule = + /// PyCapsule::new_bound_with_destructor(py, 123, None, destructor as fn(u32, *mut c_void)) + /// .unwrap(); + /// let context = Box::new(tx); // `Sender` is our context, box it up and ship it! + /// capsule.set_context(Box::into_raw(context).cast()).unwrap(); + /// // This scope will end, causing our destructor to be called... + /// }); + /// + /// assert_eq!(rx.recv(), Ok("Destructor called!".to_string())); + /// ``` + fn set_context(&self, context: *mut c_void) -> PyResult<()>; + + /// Gets the current context stored in the capsule. If there is no context, the pointer + /// will be null. + /// + /// Returns an error if this capsule is not valid. + fn context(&self) -> PyResult<*mut c_void>; + + /// Obtains a reference to the value of this capsule. + /// + /// # Safety + /// + /// It must be known that this capsule is valid and its pointer is to an item of type `T`. + unsafe fn reference(&self) -> &'py T; + + /// Gets the raw `c_void` pointer to the value in this capsule. + /// + /// Returns null if this capsule is not valid. + fn pointer(&self) -> *mut c_void; + + /// Checks if this is a valid capsule. + /// + /// Returns true if the stored `pointer()` is non-null. + fn is_valid(&self) -> bool; + + /// Retrieves the name of this capsule, if set. + /// + /// Returns an error if this capsule is not valid. + fn name(&self) -> PyResult>; +} + +impl<'py> PyCapsuleMethods<'py> for Bound<'py, PyCapsule> { + #[allow(clippy::not_unsafe_ptr_arg_deref)] + fn set_context(&self, context: *mut c_void) -> PyResult<()> { + let result = unsafe { ffi::PyCapsule_SetContext(self.as_ptr(), context) }; + if result != 0 { + Err(PyErr::fetch(self.py())) + } else { + Ok(()) + } + } + + fn context(&self) -> PyResult<*mut c_void> { + let ctx = unsafe { ffi::PyCapsule_GetContext(self.as_ptr()) }; + if ctx.is_null() { + ensure_no_error(self.py())? + } + Ok(ctx) + } + + unsafe fn reference(&self) -> &'py T { + &*self.pointer().cast() + } + + fn pointer(&self) -> *mut c_void { unsafe { - let ptr = ffi::PyCapsule_GetPointer(self.0.as_ptr(), self.name_ptr_ignore_error()); + let ptr = ffi::PyCapsule_GetPointer(self.as_ptr(), name_ptr_ignore_error(self)); if ptr.is_null() { ffi::PyErr_Clear(); } @@ -219,21 +365,15 @@ impl PyCapsule { } } - /// Checks if this is a valid capsule. - /// - /// Returns true if the stored `pointer()` is non-null. - pub fn is_valid(&self) -> bool { + fn is_valid(&self) -> bool { // As well as if the stored pointer is null, PyCapsule_IsValid also returns false if // self.as_ptr() is null or not a ptr to a PyCapsule object. Both of these are guaranteed // to not be the case thanks to invariants of this PyCapsule struct. - let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), self.name_ptr_ignore_error()) }; + let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), name_ptr_ignore_error(self)) }; r != 0 } - /// Retrieves the name of this capsule, if set. - /// - /// Returns an error if this capsule is not valid. - pub fn name(&self) -> PyResult> { + fn name(&self) -> PyResult> { unsafe { let ptr = ffi::PyCapsule_GetName(self.as_ptr()); if ptr.is_null() { @@ -244,18 +384,6 @@ impl PyCapsule { } } } - - /// Attempts to retrieve the raw name pointer of this capsule. - /// - /// On error, clears the error indicator and returns NULL. This is a private function and next - /// use of this capsule will error anyway. - fn name_ptr_ignore_error(&self) -> *const c_char { - let ptr = unsafe { ffi::PyCapsule_GetName(self.as_ptr()) }; - if ptr.is_null() { - unsafe { ffi::PyErr_Clear() }; - } - ptr - } } // C layout, as PyCapsule::get_reference depends on `T` being first. @@ -277,7 +405,7 @@ unsafe extern "C" fn capsule_destructor); + } = *Box::from_raw(ptr.cast::>()); destructor(value, ctx) } @@ -304,11 +432,20 @@ fn ensure_no_error(py: Python<'_>) -> PyResult<()> { } } +fn name_ptr_ignore_error(slf: &Bound<'_, PyCapsule>) -> *const c_char { + let ptr = unsafe { ffi::PyCapsule_GetName(slf.as_ptr()) }; + if ptr.is_null() { + unsafe { ffi::PyErr_Clear() }; + } + ptr +} + #[cfg(test)] mod tests { use libc::c_void; use crate::prelude::PyModule; + use crate::types::capsule::PyCapsuleMethods; use crate::{types::PyCapsule, Py, PyResult, Python}; use std::ffi::CString; use std::sync::mpsc::{channel, Sender}; @@ -330,7 +467,7 @@ mod tests { let foo = Foo { val: 123 }; let name = CString::new("foo").unwrap(); - let cap = PyCapsule::new(py, foo, Some(name.clone()))?; + let cap = PyCapsule::new_bound(py, foo, Some(name.clone()))?; assert!(cap.is_valid()); let foo_capi = unsafe { cap.reference::() }; @@ -349,7 +486,7 @@ mod tests { let cap: Py = Python::with_gil(|py| { let name = CString::new("foo").unwrap(); - let cap = PyCapsule::new(py, foo as fn(u32) -> u32, Some(name)).unwrap(); + let cap = PyCapsule::new_bound(py, foo as fn(u32) -> u32, Some(name)).unwrap(); cap.into() }); @@ -363,16 +500,16 @@ mod tests { fn test_pycapsule_context() -> PyResult<()> { Python::with_gil(|py| { let name = CString::new("foo").unwrap(); - let cap = PyCapsule::new(py, 0, Some(name))?; + let cap = PyCapsule::new_bound(py, 0, Some(name))?; let c = cap.context()?; assert!(c.is_null()); let ctx = Box::new(123_u32); - cap.set_context(Box::into_raw(ctx) as _)?; + cap.set_context(Box::into_raw(ctx).cast())?; let ctx_ptr: *mut c_void = cap.context()?; - let ctx = unsafe { *Box::from_raw(ctx_ptr as *mut u32) }; + let ctx = unsafe { *Box::from_raw(ctx_ptr.cast::()) }; assert_eq!(ctx, 123); Ok(()) }) @@ -389,7 +526,7 @@ mod tests { let foo = Foo { val: 123 }; let name = CString::new("builtins.capsule").unwrap(); - let capsule = PyCapsule::new(py, foo, Some(name.clone()))?; + let capsule = PyCapsule::new_bound(py, foo, Some(name.clone()))?; let module = PyModule::import(py, "builtins")?; module.add("capsule", capsule)?; @@ -412,7 +549,7 @@ mod tests { let name = CString::new("foo").unwrap(); let stuff: Vec = vec![1, 2, 3, 4]; - let cap = PyCapsule::new(py, stuff, Some(name)).unwrap(); + let cap = PyCapsule::new_bound(py, stuff, Some(name)).unwrap(); cap.into() }); @@ -429,8 +566,8 @@ mod tests { let cap: Py = Python::with_gil(|py| { let name = CString::new("foo").unwrap(); - let cap = PyCapsule::new(py, 0, Some(name)).unwrap(); - cap.set_context(Box::into_raw(Box::new(&context)) as _) + let cap = PyCapsule::new_bound(py, 0, Some(name)).unwrap(); + cap.set_context(Box::into_raw(Box::new(&context)).cast()) .unwrap(); cap.into() @@ -438,7 +575,7 @@ mod tests { Python::with_gil(|py| { let ctx_ptr: *mut c_void = cap.as_ref(py).context().unwrap(); - let ctx = unsafe { *Box::from_raw(ctx_ptr as *mut &Vec) }; + let ctx = unsafe { *Box::from_raw(ctx_ptr.cast::<&Vec>()) }; assert_eq!(ctx, &vec![1_u8, 2, 3, 4]); }) } @@ -449,14 +586,14 @@ mod tests { fn destructor(_val: u32, ctx: *mut c_void) { assert!(!ctx.is_null()); - let context = unsafe { *Box::from_raw(ctx as *mut Sender) }; + let context = unsafe { *Box::from_raw(ctx.cast::>()) }; context.send(true).unwrap(); } Python::with_gil(|py| { let name = CString::new("foo").unwrap(); - let cap = PyCapsule::new_with_destructor(py, 0, Some(name), destructor).unwrap(); - cap.set_context(Box::into_raw(Box::new(tx)) as _).unwrap(); + let cap = PyCapsule::new_bound_with_destructor(py, 0, Some(name), destructor).unwrap(); + cap.set_context(Box::into_raw(Box::new(tx)).cast()).unwrap(); }); // the destructor was called. @@ -466,7 +603,7 @@ mod tests { #[test] fn test_pycapsule_no_name() { Python::with_gil(|py| { - let cap = PyCapsule::new(py, 0usize, None).unwrap(); + let cap = PyCapsule::new_bound(py, 0usize, None).unwrap(); assert_eq!(unsafe { cap.reference::() }, &0usize); assert_eq!(cap.name().unwrap(), None); diff --git a/src/types/function.rs b/src/types/function.rs index b4492b58..c6f0f6b0 100644 --- a/src/types/function.rs +++ b/src/types/function.rs @@ -1,6 +1,7 @@ use crate::derive_utils::PyFunctionArguments; use crate::methods::PyMethodDefDestructor; use crate::prelude::*; +use crate::types::capsule::PyCapsuleMethods; use crate::{ ffi, impl_::pymethods::{self, PyMethodDef}, @@ -80,7 +81,7 @@ impl PyCFunction { ); let (def, def_destructor) = method_def.as_method_def()?; - let capsule = PyCapsule::new( + let capsule = PyCapsule::new_bound( py, ClosureDestructor:: { closure, diff --git a/src/types/mod.rs b/src/types/mod.rs index fcf843ec..e77526a2 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -277,7 +277,7 @@ pub(crate) mod any; pub(crate) mod boolobject; pub(crate) mod bytearray; pub(crate) mod bytes; -mod capsule; +pub(crate) mod capsule; #[cfg(not(Py_LIMITED_API))] mod code; mod complex;