Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pyclass methods for Bound #3792

Merged
merged 1 commit into from
Feb 5, 2024
Merged
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
220 changes: 217 additions & 3 deletions src/instance.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::err::{self, PyDowncastError, PyErr, PyResult};
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell};
use crate::pyclass::boolean_struct::{False, True};
use crate::type_object::HasPyGilRef;
Expand Down Expand Up @@ -66,6 +67,43 @@ pub unsafe trait PyNativeType: Sized {
#[repr(transparent)]
pub struct Bound<'py, T>(Python<'py>, ManuallyDrop<Py<T>>);

impl<'py, T> Bound<'py, T>
where
T: PyClass,
{
/// Creates a new instance `Bound<T>` of a `#[pyclass]` on the Python heap.
///
/// # Examples
///
/// ```rust
/// use pyo3::prelude::*;
///
/// #[pyclass]
/// struct Foo {/* fields omitted */}
///
/// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<Py<Foo>> {
/// let foo: Bound<'_, Foo> = Bound::new(py, Foo {})?;
/// Ok(foo.into())
/// })?;
/// # Ok(())
/// # }
/// ```
pub fn new(
py: Python<'py>,
value: impl Into<PyClassInitializer<T>>,
) -> PyResult<Bound<'py, T>> {
let initializer = value.into();
let obj = initializer.create_cell(py)?;
let ob = unsafe {
obj.cast::<ffi::PyObject>()
.assume_owned(py)
.downcast_into_unchecked()
};
Ok(ob)
}
}

impl<'py> Bound<'py, PyAny> {
/// Constructs a new Bound from a pointer. Panics if ptr is null.
///
Expand Down Expand Up @@ -101,6 +139,149 @@ impl<'py> Bound<'py, PyAny> {
}
}

impl<'py, T> Bound<'py, T>
where
T: PyClass,
{
/// Immutably borrows the value `T`.
///
/// This borrow lasts while the returned [`PyRef`] exists.
/// Multiple immutable borrows can be taken out at the same time.
///
/// For frozen classes, the simpler [`get`][Self::get] is available.
///
/// # Examples
///
/// ```rust
/// # use pyo3::prelude::*;
/// #
/// #[pyclass]
/// struct Foo {
/// inner: u8,
/// }
///
/// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<()> {
/// let foo: Bound<'_, Foo> = Bound::new(py, Foo { inner: 73 })?;
/// let inner: &u8 = &foo.borrow().inner;
///
/// assert_eq!(*inner, 73);
/// Ok(())
/// })?;
/// # Ok(())
/// # }
/// ```
///
/// # Panics
///
/// Panics if the value is currently mutably borrowed. For a non-panicking variant, use
/// [`try_borrow`](#method.try_borrow).
pub fn borrow(&'py self) -> PyRef<'py, T> {
self.get_cell().borrow()
}

/// Mutably borrows the value `T`.
///
/// This borrow lasts while the returned [`PyRefMut`] exists.
///
/// # Examples
///
/// ```
/// # use pyo3::prelude::*;
/// #
/// #[pyclass]
/// struct Foo {
/// inner: u8,
/// }
///
/// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<()> {
/// let foo: Bound<'_, Foo> = Bound::new(py, Foo { inner: 73 })?;
/// foo.borrow_mut().inner = 35;
///
/// assert_eq!(foo.borrow().inner, 35);
/// Ok(())
/// })?;
/// # Ok(())
/// # }
/// ```
///
/// # Panics
/// Panics if the value is currently borrowed. For a non-panicking variant, use
/// [`try_borrow_mut`](#method.try_borrow_mut).
pub fn borrow_mut(&'py self) -> PyRefMut<'py, T>
where
T: PyClass<Frozen = False>,
{
self.get_cell().borrow_mut()
}

/// Attempts to immutably borrow the value `T`, returning an error if the value is currently mutably borrowed.
///
/// The borrow lasts while the returned [`PyRef`] exists.
///
/// This is the non-panicking variant of [`borrow`](#method.borrow).
///
/// For frozen classes, the simpler [`get`][Self::get] is available.
pub fn try_borrow(&'py self) -> Result<PyRef<'py, T>, PyBorrowError> {
self.get_cell().try_borrow()
}

/// Attempts to mutably borrow the value `T`, returning an error if the value is currently borrowed.
///
/// The borrow lasts while the returned [`PyRefMut`] exists.
///
/// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut).
pub fn try_borrow_mut(&'py self) -> Result<PyRefMut<'py, T>, PyBorrowMutError>
where
T: PyClass<Frozen = False>,
{
self.get_cell().try_borrow_mut()
}

/// Provide an immutable borrow of the value `T` without acquiring the GIL.
///
/// This is available if the class is [`frozen`][macro@crate::pyclass] and [`Sync`].
///
/// # Examples
///
/// ```
/// use std::sync::atomic::{AtomicUsize, Ordering};
/// # use pyo3::prelude::*;
///
/// #[pyclass(frozen)]
/// struct FrozenCounter {
/// value: AtomicUsize,
/// }
///
/// Python::with_gil(|py| {
/// let counter = FrozenCounter { value: AtomicUsize::new(0) };
///
/// let py_counter = Bound::new(py, counter).unwrap();
///
/// py_counter.get().value.fetch_add(1, Ordering::Relaxed);
/// });
/// ```
pub fn get(&self) -> &T
where
T: PyClass<Frozen = True> + Sync,
{
let cell = self.get_cell();
// SAFETY: The class itself is frozen and `Sync` and we do not access anything but `cell.contents.value`.
unsafe { &*cell.get_ptr() }
}

fn get_cell(&'py self) -> &'py PyCell<T> {
let cell = self.as_ptr().cast::<PyCell<T>>();
// SAFETY: Bound<T> is known to contain an object which is laid out in memory as a
// PyCell<T>.
//
// Strictly speaking for now `&'py PyCell<T>` is part of the "GIL Ref" API, so this
// could use some further refactoring later to avoid going through this reference.
unsafe { &*cell }
}
}

impl<'py, T> std::fmt::Debug for Bound<'py, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let any = self.as_any();
Expand Down Expand Up @@ -1757,12 +1938,11 @@ a = A()

use super::*;

#[crate::pyclass]
#[pyo3(crate = "crate")]
#[crate::pyclass(crate = "crate")]
struct SomeClass(i32);

#[test]
fn instance_borrow_methods() {
fn py_borrow_methods() {
// More detailed tests of the underlying semantics in pycell.rs
Python::with_gil(|py| {
let instance = Py::new(py, SomeClass(0)).unwrap();
Expand All @@ -1780,6 +1960,40 @@ a = A()
})
}

#[test]
fn bound_borrow_methods() {
// More detailed tests of the underlying semantics in pycell.rs
Python::with_gil(|py| {
let instance = Bound::new(py, SomeClass(0)).unwrap();
assert_eq!(instance.borrow().0, 0);
assert_eq!(instance.try_borrow().unwrap().0, 0);
assert_eq!(instance.borrow_mut().0, 0);
assert_eq!(instance.try_borrow_mut().unwrap().0, 0);

instance.borrow_mut().0 = 123;

assert_eq!(instance.borrow().0, 123);
assert_eq!(instance.try_borrow().unwrap().0, 123);
assert_eq!(instance.borrow_mut().0, 123);
assert_eq!(instance.try_borrow_mut().unwrap().0, 123);
})
}

#[crate::pyclass(frozen, crate = "crate")]
struct FrozenClass(i32);

#[test]
fn test_frozen_get() {
Python::with_gil(|py| {
for i in 0..10 {
let instance = Py::new(py, FrozenClass(i)).unwrap();
assert_eq!(instance.get().0, i);

assert_eq!(instance.bind(py).get().0, i);
}
})
}

#[test]
#[allow(deprecated)]
fn cell_tryfrom() {
Expand Down
Loading