Skip to content

Commit fa2e22b

Browse files
birkenfelddavidhewittkngwyu
authored
guide: add type overview (#801)
* book: add type overview fixes #789 Co-Authored-By: David Hewitt <[email protected]> Co-Authored-By: Yuji Kanagawa <[email protected]> * Add concrete pointer types to the list. * Add suggestions from @kngwyu Co-authored-by: David Hewitt <[email protected]> Co-authored-by: Yuji Kanagawa <[email protected]>
1 parent d4281a0 commit fa2e22b

File tree

2 files changed

+160
-1
lines changed

2 files changed

+160
-1
lines changed

guide/src/SUMMARY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
- [Type Conversions](conversions.md)
88
- [Python Exceptions](exception.md)
99
- [Calling Python from Rust](python_from_rust.md)
10+
- [GIL, mutability and object types](types.md)
1011
- [Parallelism](parallelism.md)
1112
- [Debugging](debugging.md)
1213
- [Advanced Topics](advanced.md)
1314
- [Building and Distribution](building_and_distribution.md)
1415
- [PyPy support](pypy.md)
1516
- [Appendix A: PyO3 and rust-cpython](rust_cpython.md)
16-
- [Appendix B: Migration Guide](migration.md)
17+
- [Appendix B: Migration Guide](migration.md)

guide/src/types.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# GIL lifetimes, mutability and Python object types
2+
3+
On first glance, PyO3 provides a huge number of different types that can be used
4+
to wrap or refer to Python objects. This page delves into the details and gives
5+
an overview of their intended meaning, with examples when each type is best
6+
used.
7+
8+
9+
## Mutability and Rust types
10+
11+
Since Python has no concept of ownership, and works solely with boxed objects,
12+
any Python object can be referenced any number of times, and mutation is allowed
13+
from any reference.
14+
15+
The situation is helped a little by the Global Interpreter Lock (GIL), which
16+
ensures that only one thread can use the Python interpreter and its API at the
17+
same time, while non-Python operations (system calls and extension code) can
18+
unlock the GIL. (See [the section on parallelism](parallelism.md) for how to do
19+
that in PyO3.)
20+
21+
In PyO3, holding the GIL is modeled by acquiring a token of the type
22+
`Python<'py>`, which serves three purposes:
23+
24+
* It provides some global API for the Python interpreter, such as
25+
[`eval`][eval].
26+
* It can be passed to functions that require a proof of holding the GIL,
27+
such as [`PyObject::clone_ref`][clone_ref].
28+
* Its lifetime can be used to create Rust references that implicitly guarantee
29+
holding the GIL, such as [`&'py PyAny`][PyAny].
30+
31+
The latter two points are the reason why some APIs in PyO3 require the `py:
32+
Python` argument, while others don't.
33+
34+
The PyO3 API for Python objects is written such that instead of requiring a
35+
mutable Rust reference for mutating operations such as
36+
[`PyList::append`][PyList_append], a shared reference (which, in turn, can only
37+
be created through `Python<'_>` with a GIL lifetime) is sufficient.
38+
39+
However, Rust structs wrapped as Python objects (called `pyclass` types) usually
40+
*do* need `&mut` access. Due to the GIL, PyO3 *can* guarantee thread-safe acces
41+
to them, but it cannot statically guarantee uniqueness of `&mut` references once
42+
an object's ownership has been passed to the Python interpreter, ensuring
43+
references is done at runtime using `PyCell`, a scheme very similar to
44+
`std::cell::RefCell`.
45+
46+
47+
## Object types
48+
49+
### `PyObject`
50+
51+
**Represents:** a GIL independent reference to a Python object of unspecified
52+
type.
53+
54+
**Used:** Whenever you want to carry around references to "some" Python object,
55+
without caring about a GIL lifetime. For example, storing Python object
56+
references in a Rust struct that outlives the Python-Rust FFI boundary.
57+
58+
Can be cloned using Python reference counts with `.clone_ref()`.
59+
60+
**Conversions:**
61+
62+
- To `&PyAny`: `obj.as_ref(py)`
63+
- To `Py<ConcreteType>`: `obj.as_ref(py).extract::<Py<ConcreteType>>`
64+
- To `&ConcreteType` (which must be a Python native type): `obj.cast_as(py)`
65+
66+
67+
### `Py<SomeType>`
68+
69+
**Represents:** a GIL independent reference to a Python object of known type.
70+
This can be a Python native type (like `PyTuple`), or a `pyclass` type
71+
implemented in Rust.
72+
73+
**Used:** Like `PyObject`, but with a known inner type.
74+
75+
**Conversions:**
76+
77+
- To `PyObject`: `obj.to_object(py)`
78+
- To `&SomeType` or `&PyCell<SomeType>`: `obj.as_ref(py)`. For `pyclass` types
79+
implemented in Rust, you get a `PyCell` (see below). For Python native types,
80+
mutating operations through PyO3's API don't require `&mut` access.
81+
82+
**Note:** `PyObject` is semantically equivalent to `Py<PyAny>` and might be
83+
merged with it in the future.
84+
85+
86+
### `PyAny`
87+
88+
**Represents:** a Python object of unspecified type, restricted to a GIL
89+
lifetime. Currently, `PyAny` can only ever occur as a reference, usually
90+
`&PyAny`.
91+
92+
**Used:** Whenever you want to refer to some Python object only as long as
93+
holding the GIL. For example, intermediate values and arguments to
94+
`pyfunction`s or `pymethod`s implemented in Rust where any type is allowed.
95+
96+
**Conversions:**
97+
98+
- To `PyObject`: `obj.to_object(py)`
99+
100+
101+
### `PyTuple`, `PyDict`, and many more
102+
103+
**Represents:** a native Python object of known type, restricted to a GIL
104+
lifetime just like `PyAny`.
105+
106+
**Used:** Whenever you want to operate with native Python types while holding
107+
the GIL. Like `PyAny`, this is the most convenient form to use for function
108+
arguments and intermediate values.
109+
110+
**Conversions:**
111+
112+
- To `PyAny`: `obj.as_ref()`
113+
- To `Py<T>`: `Py::from(obj)`
114+
115+
116+
### `PyCell<SomeType>`
117+
118+
**Represents:** a reference to a Rust object (instance of `PyClass`) which is
119+
wrapped in a Python object. The cell part is an analog to stdlib's
120+
[`RefCell`][RefCell] to allow access to `&mut` references.
121+
122+
**Used:** for accessing pure-Rust API of the instance (members and functions
123+
taking `&SomeType` or `&mut SomeType`) while maintaining the aliasing rules of
124+
Rust references.
125+
126+
**Conversions:**
127+
128+
- From `PyAny`: `.downcast()`
129+
130+
131+
### `PyRef<SomeType>` and `PyRefMut<SomeType>`
132+
133+
**Represents:** reference wrapper types employed by `PyCell` to keep track of
134+
borrows, analog to `Ref` and `RefMut` used by `RefCell`.
135+
136+
**Used:** while borrowing a `PyCell`. They can also be used with `.extract()`
137+
on types like `Py<T>` and `PyAny` to get a reference quickly.
138+
139+
140+
141+
## Related traits and types
142+
143+
### `PyClass`
144+
145+
This trait marks structs defined in Rust that are also usable as Python classes,
146+
usually defined using the `#[pyclass]` macro.
147+
148+
### `PyNativeType`
149+
150+
This trait marks structs that mirror native Python types, such as `PyList`.
151+
152+
153+
154+
[eval]: https://docs.rs/pyo3/latest/pyo3/struct.Python.html#method.eval
155+
[clone_ref]: https://docs.rs/pyo3/latest/pyo3/struct.PyObject.html#method.clone_ref
156+
[PyAny]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyAny.html
157+
[PyList_append]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyList.html#method.append
158+
[RefCell]: https://doc.rust-lang.org/std/cell/struct.RefCell.html

0 commit comments

Comments
 (0)