Skip to content

Commit

Permalink
Improve API docs regarding when we free memory, resolves PyO3#311
Browse files Browse the repository at this point in the history
  • Loading branch information
benkay86 committed Aug 18, 2021
1 parent 336e87e commit 59765ee
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,19 @@ pub unsafe trait PyNativeType: Sized {
/// implement the [`PyGCProtocol`](crate::class::gc::PyGCProtocol). If your pyclass
/// contains other Python objects you should implement this protocol to avoid leaking memory.
///
/// # A note on Python reference counts
///
/// Dropping a [`Py`]`<T>` will eventually decrease Python's reference count
/// of the pointed-to variable, allowing Python's garbage collector to free
/// the associated memory, but this may not happen immediately. This is
/// because a [`Py`]`<T>` can be dropped at any time, but the Python reference
/// count can only be modified when the GIL is held.
///
/// If a [`Py`]`<T>` is dropped while its thread happens to be holding the
/// GIL then the Python reference count will be decreased immediately.
/// Otherwise, the reference count will be decreased the next time the GIL is
/// reacquired.
///
/// # A note on `Send` and `Sync`
///
/// Accessing this object is threadsafe, since any access to its API requires a [`Python<'py>`](crate::Python) token.
Expand Down
33 changes: 33 additions & 0 deletions src/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,39 @@ impl PartialOrd<(u8, u8, u8)> for PythonVersionInfo<'_> {
///
/// To avoid deadlocking, you should release the GIL before trying to lock a mutex, e.g. with
/// [Python::allow_threads].
///
/// # A note on Python reference counts
///
/// The [`Python`] type can be used to generate references to variables in
/// Python's memory e.g. using [`Python::eval()`] and indirectly e.g.
/// using [`PyModule::import()`], which takes a [`Python`] token
/// as one if its arguments to prove the GIL is held. The lifetime of these
/// references is bound to the GIL (more precisely the [`GILPool`], see
/// [`Python::new_pool()`]), which can cause surprising results with respect to
/// when a variable's reference count is decreased so that it can be released to
/// the Python garbage collector. For example:
///
/// ```rust
/// # use pyo3::prelude::*;
/// # use pyo3::types::PyString;
/// # fn main () -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<()> {
/// for _ in 0..10 {
/// let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
/// println!("Python says: {}", hello.to_str()?);
/// }
/// Ok(())
/// })
/// # }
/// ```
///
/// The variable `hello` is dropped at the end of each loop iteration, but the
/// lifetime of the pointed-to memory is bound to the [`GILPool`] and will not
/// be dropped until the [`GILPool`] is dropped at the end of
/// [`Python::with_gil()`]. Only then is each `hello` variable's Python
/// reference count decreased. This means at the last line of the example there
/// are 10 copies of `hello` in Python's memory, not just one as we might expect
/// from typical Rust lifetimes.
#[derive(Copy, Clone)]
pub struct Python<'p>(PhantomData<&'p GILGuard>);

Expand Down

0 comments on commit 59765ee

Please sign in to comment.