Write docs for PyCell, PyRef, PyRefMut

This commit is contained in:
kngwyu 2020-02-21 21:12:32 +09:00
parent 3d0ee2a28d
commit 043b13046a
3 changed files with 84 additions and 11 deletions

View File

@ -122,10 +122,10 @@ like [std::cell::RefCell](https://doc.rust-lang.org/std/cell/struct.RefCell.html
Users who are familiar with `RefCell` can use `PyCell` just like `RefCell`.
For users who doesn't know `RefCell` well, we repeat the Rust's borrowing rule here:
For users who doesn't know `RefCell` well, here we repeat the Rust's borrowing rule:
- At any given time, you can have either (but not both of) one mutable reference or any number of immutable references.
- References must always be valid.
`PyCell` ensures these borrowing rules by managing a reference counter.
`PyCell` ensures these borrowing rules by tracking references at runtime.
TODO: link to the API document

View File

@ -182,8 +182,8 @@ impl<T: IntoPy<PyObject>> IntoPyResult<T> for PyResult<T> {
}
}
/// Variant of IntoPyResult for the specific case of #[new]. In the case of returning (Sub, Base)
/// from #[new], IntoPyResult can't apply because (Sub, Base) doesn't implement IntoPy<PyObject>.
/// Variant of IntoPyResult for the specific case of `#[new]`. In the case of returning (Sub, Base)
/// from `#[new]`, IntoPyResult can't apply because (Sub, Base) doesn't implement IntoPy<PyObject>.
pub trait IntoPyNewResult<T: PyClass, I: Into<PyClassInitializer<T>>> {
fn into_pynew_result(self) -> PyResult<I>;
}

View File

@ -1,4 +1,4 @@
//! Traits and structs for `#[pyclass]`.
//! Includes `PyCell` implementation.
use crate::conversion::{AsPyPointer, FromPyPointer, ToPyObject};
use crate::pyclass_init::PyClassInitializer;
use crate::pyclass_slots::{PyClassDict, PyClassWeakRef};
@ -90,15 +90,21 @@ impl<T: PyClass> PyCellInner<T> {
}
}
// TODO(kngwyu): Mutability example
/// `PyCell` represents the concrete layout of `T: PyClass` when it is converted
/// to a Python class.
/// `PyCell` is the container type for [`PyClass`](../pyclass/trait.PyClass.html).
///
/// You can use it to test your `#[pyclass]` correctly works.
/// From Python side, `PyCell<T>` is the concrete layout of `T: PyClass` in the Python heap,
/// which means we can convert `*const PyClass<T>` to `*mut ffi::PyObject`.
///
/// From Rust side, `PyCell<T>` is the mutable container of `T`.
/// Since `PyCell<T: PyClass>` is always on the Python heap, we don't have the ownership of it.
/// Thus, to mutate the data behind `&PyCell<T>` safely, we employ the
/// [Interior Mutability Pattern](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html)
/// like [std::cell::RefCell](https://doc.rust-lang.org/std/cell/struct.RefCell.html).
///
/// In most cases, `PyCell` is hidden behind `#[pymethods]`.
/// However, you can construct `&PyCell` directly to test your pyclass in Rust code.
/// ```
/// # use pyo3::prelude::*;
/// # use pyo3::{py_run, PyCell};
/// #[pyclass]
/// struct Book {
/// #[pyo3(get)]
@ -112,7 +118,34 @@ impl<T: PyClass> PyCellInner<T> {
/// author: "Philip Kindred Dick",
/// };
/// let book_cell = PyCell::new(py, book).unwrap();
/// py_run!(py, book_cell, "assert book_cell.name[-6:] == 'Castle'");
/// // you can expose PyCell to Python snippets
/// pyo3::py_run!(py, book_cell, "assert book_cell.name[-6:] == 'Castle'");
/// ```
/// You can also use `slf: &PyCell<Self>` as an alternative `self` receiver of `#[pymethod]`.
/// ```
/// # use pyo3::prelude::*;
/// use std::collections::HashMap;
/// #[pyclass]
/// #[derive(Default)]
/// struct Counter {
/// data: HashMap<String, usize>,
/// }
/// #[pymethods]
/// impl Counter {
/// fn increment(slf: &PyCell<Self>, name: String) -> PyResult<usize> {
/// let mut slf_mut = slf.try_borrow_mut()?;
/// // Now a mutable reference exists so we cannot another one
/// assert!(slf.try_borrow().is_err());
/// assert!(slf.try_borrow_mut().is_err());
/// let counter = slf_mut.data.entry(name).or_insert(0);
/// *counter += 1;
/// Ok(*counter)
/// }
/// }
/// # let gil = Python::acquire_gil();
/// # let py = gil.python();
/// # let counter = PyCell::new(py, Counter::default()).unwrap();
/// # pyo3::py_run!(py, counter, "assert counter.increment('cat') == 1");
/// ```
#[repr(C)]
pub struct PyCell<T: PyClass> {
@ -254,6 +287,43 @@ impl<T: PyClass + fmt::Debug> fmt::Debug for PyCell<T> {
}
}
/// Wraps a borrowed reference to a value in a `PyCell<T>`.
///
/// See the [`PyCell`](struct.PyCell.html) documentation for more.
/// # Example
/// You can use `PyRef` as an alternative of `&self` receiver when
/// - You need to access the pointer of `PyCell`.
/// - You want to get super class.
/// ```
/// # use pyo3::prelude::*;
/// #[pyclass]
/// struct Parent {
/// basename: &'static str,
/// }
/// #[pyclass(extends=Parent)]
/// struct Child {
/// name: &'static str,
/// }
/// #[pymethods]
/// impl Child {
/// #[new]
/// fn new() -> (Self, Parent) {
/// (Child { name: "Caterpillar" }, Parent { basename: "Butterfly" })
/// }
/// fn format(slf: PyRef<Self>) -> String {
/// // We can get *mut ffi::PyObject from PyRef
/// use pyo3::AsPyPointer;
/// let refcnt = unsafe { pyo3::ffi::Py_REFCNT(slf.as_ptr()) };
/// // We can get &Self::BaseType by as_ref
/// let basename = slf.as_ref().basename;
/// format!("{}(base: {}, cnt: {})", slf.name, basename, refcnt)
/// }
/// }
/// # let gil = Python::acquire_gil();
/// # let py = gil.python();
/// # let sub = PyCell::new(py, Child::new()).unwrap();
/// # pyo3::py_run!(py, sub, "assert sub.format() == 'Caterpillar(base: Butterfly, cnt: 4)'");
/// ```
pub struct PyRef<'p, T: PyClass> {
inner: &'p PyCellInner<T>,
}
@ -319,6 +389,9 @@ impl<T: PyClass + fmt::Debug> fmt::Debug for PyRef<'_, T> {
}
}
/// Wraps a mutable borrowed reference to a value in a `PyCell<T>`.
///
/// See the [`PyCell`](struct.PyCell.html) and [`PyRef`](struct.PyRef.html) documentations for more.
pub struct PyRefMut<'p, T: PyClass> {
inner: &'p PyCellInner<T>,
}