Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions newsfragments/5831.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `Borrowed::as_unbound`
1 change: 1 addition & 0 deletions newsfragments/5831.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make `IntoPyObject` for `Bound` & `Borrowed` more generic
24 changes: 12 additions & 12 deletions src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,55 +127,55 @@ pub(crate) mod private {
}
}

impl<'py, T: PyTypeCheck> IntoPyObject<'py> for Bound<'py, T> {
impl<'py, T: PyTypeCheck> IntoPyObject<'py> for Bound<'_, T> {
type Target = T;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;

#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;

fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self)
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.unbind().into_bound(py))
}
}

impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &'a Bound<'py, T> {
impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &'a Bound<'_, T> {
type Target = T;
type Output = Borrowed<'a, 'py, Self::Target>;
type Error = Infallible;

#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;

fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.as_borrowed())
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.as_unbound().bind_borrowed(py))
}
}

impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for Borrowed<'a, 'py, T> {
impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for Borrowed<'a, '_, T> {
type Target = T;
type Output = Borrowed<'a, 'py, Self::Target>;
type Error = Infallible;

#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;

fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self)
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.as_unbound().bind_borrowed(py))
}
}

impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &Borrowed<'a, 'py, T> {
impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &Borrowed<'a, '_, T> {
type Target = T;
type Output = Borrowed<'a, 'py, Self::Target>;
type Error = Infallible;

#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;

fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(*self)
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.as_unbound().bind_borrowed(py))
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,14 @@ impl<'a, 'py, T> Borrowed<'a, 'py, T> {
pub unsafe fn cast_unchecked<U>(self) -> Borrowed<'a, 'py, U> {
Borrowed(self.0, PhantomData, self.2)
}

/// Removes the connection for this `Borrowed<T>` from the [`Python<'py>`] token,
/// allowing it to cross thread boundaries, without transferring ownership.
#[inline]
pub fn as_unbound(&self) -> &'a Py<T> {
// Safety: NonNull<ffi::PyObject> is layout-compatible with Py<T>
unsafe { NonNull::from(&self.0).cast().as_ref() }
}
Comment on lines +1082 to +1085
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be unsound. I think the &Py can only live as long as the reference to the Borrowed (&self) and not for 'a, as the pointer we get from self is only valid until the borrow ends at the end of the scope.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're absolutely right, thanks for catching this and sorry I missed it review!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad I could catch it. Opened #5832 to revert this for now.

Copy link
Member Author

@bschoenmaeckers bschoenmaeckers Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for my mistake, I will take a closer look tomorrow so I understand the relationship between these lifetimes better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, I made a similar form of exactly this mistake many times when we were trying to implement the Bound API originally!

}

impl<'a, T: PyClass> Borrowed<'a, '_, T> {
Expand Down
Loading