Skip to content

Commit

Permalink
Improve guide for new PyObject
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Apr 26, 2020
1 parent 2babe1c commit a566b8d
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 28 deletions.
121 changes: 93 additions & 28 deletions guide/src/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,50 +52,63 @@ references is done at runtime using `PyCell`, a scheme very similar to
lifetime. Currently, `PyObject` can only ever occur as a reference, usually
`&PyObject`.

**Used:** Whenever you want to refer to some Python object only as long as
holding the GIL. For example, intermediate values and arguments to
`pyfunction`s or `pymethod`s implemented in Rust where any type is allowed.
**Used:** This is the base type for working with Python objects in pyo3. Use
it whenever you are not concerned about the actual type of a Python value.
For example, intermediate values and arguments to `pyfunction`s or `pymethod`s
implemented in Rust where any type is allowed.

**Conversions:**

- To `Py<Any>`: `obj.into()`
- To `PyDict`, etc.: `obj.downcast()`
- To rust `Vec<T>` etc.: `obj.extract()`

### `Py<SomeType>`

**Represents:** a GIL independent reference to a Python object of known type.
This can be a Python native type (like `PyTuple`), or a `pyclass` type
implemented in Rust.

**Used:** Whenever you want to carry around references to "some" Python object,
without caring about a GIL lifetime. For example, storing Python object
references in a Rust struct that outlives the Python-Rust FFI boundary,
or returning objects from functions implemented in Rust back to Python.

**Conversions:**

- To `&SomeType` or `&PyCell<SomeType>`: `obj.as_ref(py)`. For `pyclass` types
implemented in Rust, you get a `PyCell` (see below). For Python native types,
mutating operations through PyO3's API don't require `&mut` access.
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyDict;
#
# fn example() -> PyResult<()> {
# let gil = Python::acquire_gil();
# let py = gil.python();
#
// Convert `PyObject` to `PyDict` etc. using `.downcast()`
# let obj: &PyObject = PyDict::new(py).as_ref();
let dict: &PyDict = obj.downcast()?;

// Convert `PyObject` to owned Rust structs like `Vec<T>` using `.extract()`
# let obj: &PyObject = vec![1, 2, 3].to_object(py);
let v: Vec<i32> = obj.extract()?;

// Convert `PyObject` to `Py<PyObject>` using `.into()`
let p: Py<PyObject> = obj.into();
# Ok(()) }
# example();
```


### `PyTuple`, `PyDict`, and many more

**Represents:** a native Python object of known type, restricted to a GIL
lifetime just like `PyObject`.
lifetime just like `PyObject`. They are accessible only through references in
the same way, as `&PyTuple`, `&PyDict` etc.

**Used:** Whenever you want to operate with native Python types while holding
the GIL. Like `PyObject`, this is the most convenient form to use for function
arguments and intermediate values.

**Conversions:**

- To `PyObject`: `obj.as_ref()`
- To `Py<T>`: `Py::from(obj)`
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyDict;
# let gil = Python::acquire_gil();
# let py = gil.python();
// Convert to `PyObject` using `.as_ref()`
# let dict = PyDict::new(py);
let obj: &PyObject = dict.as_ref();

// Convert to `Py<T>` using `.into()`
let p: Py<PyDict> = dict.into();
```


### `PyCell<SomeType>`
### `PyCell<T>`

**Represents:** a reference to a Rust object (instance of `PyClass`) which is
wrapped in a Python object. The cell part is an analog to stdlib's
Expand All @@ -107,7 +120,33 @@ Rust references.

**Conversions:**

- From `PyObject`: `.downcast()`
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyDict;
#
# #[pyclass]
# struct MyClass { }
#
# fn example() -> PyResult<()> {
# let gil = Python::acquire_gil();
# let py = gil.python();
#
// Convert `PyObject` to `PyCell` using `.downcast()`
# let obj: &PyObject = PyCell::new(py, MyClass { })?.to_object(py);
let pycell: &PyCell<MyClass> = obj.downcast()?;

// Convert `PyCell<T>` to `PyRef<T>` using `.try_borrow()`
let pyref: PyRef<MyClass> = pycell.try_borrow()?;
# drop(pyref);

// Convert `PyCell<T>` to `PyRefMut<T>` using `.try_borrow_mut()`
let pyrefmut: PyRefMut<MyClass> = pycell.try_borrow_mut()?;

// Convert `PyCell<T>` to `Py<T>` using `.into()`
let pyrefmut: Py<MyClass> = pycell.into();
# Ok(()) }
# example();
```


### `PyRef<SomeType>` and `PyRefMut<SomeType>`
Expand All @@ -119,6 +158,32 @@ borrows, analog to `Ref` and `RefMut` used by `RefCell`.
on types like `Py<T>` and `PyObject` to get a reference quickly.


## Outliving the GIL Lifetime

In some cases you may need to create a Python object which is not bounded by
the GIL lifetime. For example, you might not want to bother with annotating
lifetimes in a complicated function signature. Or you might want to store a
Python object inside of a Rust struct.

For these cases PyO3 provides the `Py<T>` smart pointer type. It can contain
any Python type, such as `Py<PyObject>`, `Py<PyTuple>`, or even `Py<T>` where
`T: PyClass`.


### Obtaining Py<T>

Converting `T` from `Py<T>` depends on what `T` is:

- For native types `PyObject`, `PyTuple` etc.: `obj.into()`
- For Rust type `T`: `PyCell::new(py, obj).into()`


### Accessing the stored object

To access the value inside a `Py<T>`, use the `Py::as_ref` method. It takes the
PyO3 GIL token `py` as its argument, so the returned `&T` is bounded by the GIL
lifetime in the usual way.


## Related traits and types

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,4 +345,5 @@ pub mod doc_test {
doctest!("../guide/src/pypy.md", guide_pypy_md);
doctest!("../guide/src/rust_cpython.md", guide_rust_cpython_md);
doctest!("../guide/src/migration.md", guide_migration_md);
doctest!("../guide/src/types.md", guide_types_md);
}

0 comments on commit a566b8d

Please sign in to comment.