diff --git a/guide/pyclass-parameters.md b/guide/pyclass-parameters.md index b471f5dd3ae..863f447080e 100644 --- a/guide/pyclass-parameters.md +++ b/guide/pyclass-parameters.md @@ -22,7 +22,7 @@ | `str` | Implements `__str__` using the `Display` implementation of the underlying Rust datatype or by passing an optional format string `str=""`. *Note: The optional format string is only allowed for structs. `name` and `rename_all` are incompatible with the optional format string. Additional details can be found in the discussion on this [PR](https://github.com/PyO3/pyo3/pull/4233).* | | `subclass` | Allows other Python classes and `#[pyclass]` to inherit from this class. Enums cannot be subclassed. | | `text_signature = "(arg1, arg2, ...)"` | Sets the text signature for the Python class' `__new__` method. | -| `unsendable` | Required if your struct is not [`Send`][params-3]. Rather than using `unsendable`, consider implementing your struct in a threadsafe way by e.g. substituting [`Rc`][params-4] with [`Arc`][params-5]. By using `unsendable`, your class will panic when accessed by another thread. Also note the Python's GC is multi-threaded and while unsendable classes will not be traversed on foreign threads to avoid UB, this can lead to memory leaks. | +| `unsendable` | Required if your struct is not [`Send`][params-3]. Rather than using `unsendable`, consider implementing your struct in a thread-safe way by e.g. substituting [`Rc`][params-4] with [`Arc`][params-5]. By using `unsendable`, your class will panic when accessed by another thread. Also note the Python's GC is multi-threaded and while unsendable classes will not be traversed on foreign threads to avoid UB, this can lead to memory leaks. | | `weakref` | Allows this class to be [weakly referenceable][params-6]. | All of these parameters can either be passed directly on the `#[pyclass(...)]` annotation, or as one or diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index f025d790b5d..f8cc899d75c 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -15,6 +15,7 @@ - [Basic object customization](class/object.md) - [Emulating numeric types](class/numeric.md) - [Emulating callable objects](class/call.md) + - [Thread safety](class/thread-safety.md) - [Calling Python from Rust](python-from-rust.md) - [Python object types](types.md) - [Python exceptions](exception.md) diff --git a/guide/src/class.md b/guide/src/class.md index 6a80fd7ad9c..5d2c8435416 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -73,7 +73,7 @@ The above example generates implementations for [`PyTypeInfo`] and [`PyClass`] f ### Restrictions -To integrate Rust types with Python, PyO3 needs to place some restrictions on the types which can be annotated with `#[pyclass]`. In particular, they must have no lifetime parameters, no generic parameters, and must implement `Send`. The reason for each of these is explained below. +To integrate Rust types with Python, PyO3 needs to place some restrictions on the types which can be annotated with `#[pyclass]`. In particular, they must have no lifetime parameters, no generic parameters, and must be thread-safe. The reason for each of these is explained below. #### No lifetime parameters @@ -119,9 +119,13 @@ create_interface!(IntClass, i64); create_interface!(FloatClass, String); ``` -#### Must be Send +#### Must be thread-safe -Because Python objects are freely shared between threads by the Python interpreter, there is no guarantee which thread will eventually drop the object. Therefore all types annotated with `#[pyclass]` must implement `Send` (unless annotated with [`#[pyclass(unsendable)]`](#customizing-the-class)). +Python objects are freely shared between threads by the Python interpreter. This means that: +- Python objects may be created and destroyed by different Python threads; therefore #[pyclass]` objects must be `Send`. +- Python objects may be accessed by multiple python threads simultaneously; therefore `#[pyclass]` objects must be `Sync`. + +For now, don't worry about these requirements; simple classes will already be thread-safe. There is a [detailed discussion on thread-safety](./class/thread-safety.md) later in the guide. ## Constructor diff --git a/guide/src/class/thread-safety.md b/guide/src/class/thread-safety.md new file mode 100644 index 00000000000..841ee6ae2db --- /dev/null +++ b/guide/src/class/thread-safety.md @@ -0,0 +1,108 @@ +# `#[pyclass]` thread safety + +Python objects are freely shared between threads by the Python interpreter. This means that: +- there is no control which thread might eventually drop the `#[pyclass]` object, meaning `Send` is required. +- multiple threads can potentially be reading the `#[pyclass]` data simultaneously, meaning `Sync` is required. + +This section of the guide discusses various data structures which can be used to make types satisfy these requirements. + +In special cases where it is known that your Python application is never going to use threads (this is rare!), these thread-safety requirements can be opted-out with [`#[pyclass(unsendable)]`](../class.md#customizing-the-class), at the cost of making concurrent access to the Rust data be runtime errors. This is only for very specific use cases; it is almost always better to make proper thread-safe types. + +## Making `#[pyclass]` types thread-safe + +The general challenge with thread-safety is to make sure that two threads cannot produce a data race, i.e. unsynchronized writes to the same data at the same time. A data race produces an unpredictable result and is forbidden by Rust. + +By default, `#[pyclass]` employs an ["interior mutability" pattern](../class.md#bound-and-interior-mutability) to allow for either multiple `&T` references or a single exclusive `&mut T` reference to access the data. This allows for simple `#[pyclass]` types to be thread-safe automatically, at the cost of runtime checking for concurrent access. Errors will be raised if the usage overlaps. + +For example, the below simple class is thread-safe: + +```rust +# use pyo3::prelude::*; + +#[pyclass] +struct MyClass { + x: i32, + y: i32, +} + +#[pymethods] +impl MyClass { + fn get_x(&self) -> i32 { + self.x + } + + fn set_y(&mut self, value: i32) { + self.y = value; + } +} +``` + +In the above example, if calls to `get_x` and `set_y` overlap (from two different threads) then at least one of those threads will experience a runtime error indicating that the data was "already borrowed". + +To avoid these errors, you can take control of the interior mutability yourself in one of the following ways. + +### Using atomic data structures + +To remove the possibility of having overlapping `&self` and `&mut self` references produce runtime errors, consider using `#[pyclass(frozen)]` and use [atomic data structures](https://doc.rust-lang.org/std/sync/atomic/) to control modifications directly. + +For example, a thread-safe version of the above `MyClass` using atomic integers would be as follows: + +```rust +# use pyo3::prelude::*; +use std::sync::atomic::{AtomicI32, Ordering}; + +#[pyclass(frozen)] +struct MyClass { + x: AtomicI32, + y: AtomicI32, +} + +#[pymethods] +impl MyClass { + fn get_x(&self) -> i32 { + self.x.load(Ordering::Relaxed) + } + + fn set_y(&self, value: i32) { + self.y.store(value, Ordering::Relaxed) + } +} +``` + +### Using locks + +An alternative to atomic data structures is to use [locks](https://doc.rust-lang.org/std/sync/struct.Mutex.html) to make threads wait for access to shared data. + +For example, a thread-safe version of the above `MyClass` using locks would be as follows: + +```rust +# use pyo3::prelude::*; +use std::sync::Mutex; + +struct MyClassInner { + x: i32, + y: i32, +} + +#[pyclass(frozen)] +struct MyClass { + inner: Mutex +} + +#[pymethods] +impl MyClass { + fn get_x(&self) -> i32 { + self.inner.lock().expect("lock not poisoned").x + } + + fn set_y(&self, value: i32) { + self.inner.lock().expect("lock not poisoned").y = value; + } +} +``` + +### Wrapping unsynchronized data + +In some cases, the data structures stored within a `#[pyclass]` may themselves not be thread-safe. Rust will therefore not implement `Send` and `Sync` on the `#[pyclass]` type. + +To achieve thread-safety, a manual `Send` and `Sync` implementation is required which is `unsafe` and should only be done following careful review of the soundness of the implementation. Doing this for PyO3 types is no different than for any other Rust code, [the Rustonomicon](https://doc.rust-lang.org/nomicon/send-and-sync.html) has a great discussion on this. diff --git a/guide/src/free-threading.md b/guide/src/free-threading.md index 4a326e58e98..d867a707795 100644 --- a/guide/src/free-threading.md +++ b/guide/src/free-threading.md @@ -49,6 +49,8 @@ annotate Python modules declared by rust code in your project to declare that they support free-threaded Python, for example by declaring the module with `#[pymodule(gil_used = false)]`. +More complicated `#[pyclass]` types may need to deal with thread-safety directly; there is [a dedicated section of the guide](./class/thread-safety.md) to discuss this. + At a low-level, annotating a module sets the `Py_MOD_GIL` slot on modules defined by an extension to `Py_MOD_GIL_NOT_USED`, which allows the interpreter to see at runtime that the author of the extension thinks the extension is diff --git a/guide/src/migration.md b/guide/src/migration.md index 1b8400604cb..5b80244aa58 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -4,107 +4,136 @@ This guide can help you upgrade code through breaking changes from one PyO3 vers For a detailed list of all changes, see the [CHANGELOG](changelog.md). ## from 0.22.* to 0.23 +
+Click to expand -### `gil-refs` feature removed +PyO3 0.23 is a significant rework of PyO3's internals for two major improvements: + - Support of Python 3.13's new freethreaded build (aka "3.13t") + - Rework of to-Python conversions with a new `IntoPyObject` trait. + +These changes are both substantial and reasonable efforts have been made to allow as much code as possible to continue to work as-is despite the changes. The impacts are likely to be seen in three places when upgrading: + - PyO3's data structures [are now thread-safe](#free-threaded-python-support) instead of reliant on the GIL for synchronization. In particular, `#[pyclass]` types are [now required to be `Sync`](./class/thread-safety.md). + - The [`IntoPyObject` trait](#new-intopyobject-trait-unifies-to-python-conversions) may need to be implemented for types in your codebase. In most cases this can simply be done with [`#[derive(IntoPyObject)]`](#intopyobject-and-intopyobjectref-derive-macros). There will be many deprecation warnings from the replacement of `IntoPy` and `ToPyObject` traits. + - There will be many deprecation warnings from the [final removal of the `gil-refs` feature](#gil-refs-feature-removed), which opened up API space for a cleanup and simplification to PyO3's "Bound" API. + +The sections below discuss the rationale and details of each change in more depth. +
+ +### Free-threaded Python Support
Click to expand -PyO3 0.23 completes the removal of the "GIL Refs" API in favour of the new "Bound" API introduced in PyO3 0.21. +PyO3 0.23 introduces initial support for the new free-threaded build of +CPython 3.13, aka "3.13t". -With the removal of the old API, many "Bound" API functions which had been introduced with `_bound` suffixes no longer need the suffixes as these names has been freed up. For example, `PyTuple::new_bound` is now just `PyTuple::new` (the existing name remains but is deprecated). +Because this build allows multiple Python threads to operate simultaneously on underlying Rust data, the `#[pyclass]` macro now requires that types it operates on implement `Sync`. -Before: +Aside from the change to `#[pyclass]`, most features of PyO3 work unchanged, as the changes have been to the internal data structures to make them thread-safe. An example of this is the `GILOnceCell` type, which used the GIL to synchronize single-initialization. It now uses internal locks to guarantee that only one write ever succeeds, however it allows for multiple racing runs of the initialization closure. It may be preferable to instead use `std::sync::OnceLock` in combination with the `pyo3::sync::OnceLockExt` trait which adds `OnceLock::get_or_init_py_attached` for single-initialization where the initialization closure is guaranteed only ever to run once and without deadlocking with the GIL. -```rust -# #![allow(deprecated)] -# use pyo3::prelude::*; -# use pyo3::types::PyTuple; -# fn main() { -# Python::with_gil(|py| { -// For example, for PyTuple. Many such APIs have been changed. -let tup = PyTuple::new_bound(py, [1, 2, 3]); -# }) -# } -``` +Future PyO3 versions will likely add more traits and data structures to make working with free-threaded Python easier. -After: +Some features are unaccessible on the free-threaded build: + - The `GILProtected` type, which relied on the GIL to expose synchronized access to inner contents + - `PyList::get_item_unchecked`, which cannot soundly be used due to races between time-of-check and time-of-use -```rust -# use pyo3::prelude::*; -# use pyo3::types::PyTuple; -# fn main() { -# Python::with_gil(|py| { -// For example, for PyTuple. Many such APIs have been changed. -let tup = PyTuple::new(py, [1, 2, 3]); -# }) -# } -``` +If you make use of these features then you will need to account for the +unavailability of the API in the free-threaded build. One way to handle it is via conditional compilation -- extensions can use `pyo3-build-config` to get access to a `#[cfg(Py_GIL_DISABLED)]` guard. + +See [the guide section on free-threaded Python](free-threading.md) for more details about supporting free-threaded Python in your PyO3 extensions.
-### Renamed `IntoPyDict::into_py_dict_bound` into `IntoPyDict::into_py_dict`. +### New `IntoPyObject` trait unifies to-Python conversions
Click to expand -The `IntoPyDict::into_py_dict_bound` method has been renamed to `IntoPyDict::into_py_dict` and is now fallible. If you implemented `IntoPyDict` for your type, you should implement `into_py_dict` instead of `into_py_dict_bound`. The old name is still available but deprecated. +PyO3 0.23 introduces a new `IntoPyObject` trait to convert Rust types into Python objects which replaces both `IntoPy` and `ToPyObject`. +Notable features of this new trait include: +- conversions can now return an error +- it is designed to work efficiently for both `T` owned types and `&T` references +- compared to `IntoPy` the generic `T` moved into an associated type, so + - there is now only one way to convert a given type + - the output type is stronger typed and may return any Python type instead of just `PyAny` +- byte collections are specialized to convert into `PyBytes` now, see [below](#to-python-conversions-changed-for-byte-collections-vecu8-u8-n-and-smallvecu8-n) +- `()` (unit) is now only specialized in return position of `#[pyfunction]` and `#[pymethods]` to return `None`, in normal usage it converts into an empty `PyTuple` -Before: +All PyO3 provided types as well as `#[pyclass]`es already implement `IntoPyObject`. Other types will +need to adapt an implementation of `IntoPyObject` to stay compatible with the Python APIs. In many cases +the new [`#[derive(IntoPyObject)]`](#intopyobject-and-intopyobjectref-derive-macros) macro can be used instead of +[manual implementations](#intopyobject-manual-implementation). +Together with the introduction of `IntoPyObject` the old conversion traits `ToPyObject` and `IntoPy` +are deprecated and will be removed in a future PyO3 version. + +#### `IntoPyObject` and `IntoPyObjectRef` derive macros + +To implement the new trait you may use the new `IntoPyObject` and `IntoPyObjectRef` derive macros as below. + +```rust +# use pyo3::prelude::*; +#[derive(IntoPyObject, IntoPyObjectRef)] +struct Struct { + count: usize, + obj: Py, +} +``` + +The `IntoPyObjectRef` derive macro derives implementations for references (e.g. for `&Struct` in the example above), which is a replacement for the `ToPyObject` trait. + +#### `IntoPyObject` manual implementation + +Before: ```rust,ignore # use pyo3::prelude::*; -# use pyo3::types::{PyDict, IntoPyDict}; -# use std::collections::HashMap; +# #[allow(dead_code)] +struct MyPyObjectWrapper(PyObject); -struct MyMap(HashMap); +impl IntoPy for MyPyObjectWrapper { + fn into_py(self, py: Python<'_>) -> PyObject { + self.0 + } +} -impl IntoPyDict for MyMap -where - K: ToPyObject, - V: ToPyObject, -{ - fn into_py_dict_bound(self, py: Python<'_>) -> Bound<'_, PyDict> { - let dict = PyDict::new_bound(py); - for (key, value) in self.0 { - dict.set_item(key, value) - .expect("Failed to set_item on dict"); - } - dict +impl ToPyObject for MyPyObjectWrapper { + fn to_object(&self, py: Python<'_>) -> PyObject { + self.0.clone_ref(py) } } ``` After: - ```rust # use pyo3::prelude::*; -# use pyo3::types::{PyDict, IntoPyDict}; -# use std::collections::HashMap; - # #[allow(dead_code)] -# struct MyMap(HashMap); +# struct MyPyObjectWrapper(PyObject); -impl<'py, K, V> IntoPyDict<'py> for MyMap -where - K: IntoPyObject<'py>, - V: IntoPyObject<'py>, -{ - fn into_py_dict(self, py: Python<'py>) -> PyResult> { - let dict = PyDict::new(py); - for (key, value) in self.0 { - dict.set_item(key, value)?; - } - Ok(dict) +impl<'py> IntoPyObject<'py> for MyPyObjectWrapper { + type Target = PyAny; // the Python type + type Output = Bound<'py, Self::Target>; // in most cases this will be `Bound` + type Error = std::convert::Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(self.0.into_bound(py)) + } +} + +// `ToPyObject` implementations should be converted to implementations on reference types +impl<'a, 'py> IntoPyObject<'py> for &'a MyPyObjectWrapper { + type Target = PyAny; + type Output = Borrowed<'a, 'py, Self::Target>; // `Borrowed` can be used to optimized reference counting + type Error = std::convert::Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(self.0.bind_borrowed(py)) } } ```
-### Macro conversion changed for byte collections (`Vec`, `[u8; N]` and `SmallVec<[u8; N]>`). +### To-Python conversions changed for byte collections (`Vec`, `[u8; N]` and `SmallVec<[u8; N]>`).
Click to expand -PyO3 0.23 introduced the new fallible conversion trait `IntoPyObject`. The `#[pyfunction]` and -`#[pymethods]` macros prefer `IntoPyObject` implementations over `IntoPy`. This also -applies to `#[pyo3(get)]`. +With the introduction of the `IntoPyObject` trait, PyO3's macros now prefer `IntoPyObject` implementations over `IntoPy` when producing Python values. This applies to `#[pyfunction]` and `#[pymethods]` return values and also fields accessed via `#[pyo3(get)]`. This change has an effect on functions and methods returning _byte_ collections like - `Vec` @@ -130,8 +159,7 @@ fn bar() -> Vec { // unaffected, returns `PyList` If this conversion is _not_ desired, consider building a list manually using `PyList::new`. -The following types were previously _only_ implemented for `u8` and now allow other `T`s turn into -`PyList` +The following types were previously _only_ implemented for `u8` and now allow other `T`s turn into `PyList`: - `&[T]` - `Cow<[T]>` @@ -139,116 +167,102 @@ This is purely additional and should just extend the possible return types.
-### Python API trait bounds changed +### `gil-refs` feature removed
Click to expand -PyO3 0.23 introduces a new unified `IntoPyObject` trait to convert Rust types into Python objects. -Notable features of this new trait: -- conversions can now return an error -- compared to `IntoPy` the generic `T` moved into an associated type, so - - there is now only one way to convert a given type - - the output type is stronger typed and may return any Python type instead of just `PyAny` -- byte collections are special handled and convert into `PyBytes` now, see [above](#macro-conversion-changed-for-byte-collections-vecu8-u8-n-and-smallvecu8-n) -- `()` (unit) is now only special handled in return position and otherwise converts into an empty `PyTuple` +PyO3 0.23 completes the removal of the "GIL Refs" API in favour of the new "Bound" API introduced in PyO3 0.21. -All PyO3 provided types as well as `#[pyclass]`es already implement `IntoPyObject`. Other types will -need to adapt an implementation of `IntoPyObject` to stay compatible with the Python APIs. In many cases -the new [`#[derive(IntoPyObject)]`](#intopyobject-derive-macro) macro can be used instead of -[manual implementations](#intopyobject-manual-implementation). +With the removal of the old API, many "Bound" API functions which had been introduced with `_bound` suffixes no longer need the suffixes as these names have been freed up. For example, `PyTuple::new_bound` is now just `PyTuple::new` (the existing name remains but is deprecated). -Together with the introduction of `IntoPyObject` the old conversion traits `ToPyObject` and `IntoPy` -are deprecated and will be removed in a future PyO3 version. +Before: -#### `IntoPyObject` derive macro +```rust +# #![allow(deprecated)] +# use pyo3::prelude::*; +# use pyo3::types::PyTuple; +# fn main() { +# Python::with_gil(|py| { +// For example, for PyTuple. Many such APIs have been changed. +let tup = PyTuple::new_bound(py, [1, 2, 3]); +# }) +# } +``` -To migrate you may use the new `IntoPyObject` derive macro as below. +After: ```rust # use pyo3::prelude::*; -#[derive(IntoPyObject)] -struct Struct { - count: usize, - obj: Py, -} +# use pyo3::types::PyTuple; +# fn main() { +# Python::with_gil(|py| { +// For example, for PyTuple. Many such APIs have been changed. +let tup = PyTuple::new(py, [1, 2, 3]); +# }) +# } ``` +#### `IntoPyDict` trait adjusted for removal of `gil-refs` -#### `IntoPyObject` manual implementation +As part of this API simplification, the `IntoPyDict` trait has had a small breaking change: `IntoPyDict::into_py_dict_bound` method has been renamed to `IntoPyDict::into_py_dict`. It is also now fallible as part of the `IntoPyObject` trait addition. + +If you implemented `IntoPyDict` for your type, you should implement `into_py_dict` instead of `into_py_dict_bound`. The old name is still available for calling but deprecated. Before: + ```rust,ignore # use pyo3::prelude::*; -# #[allow(dead_code)] -struct MyPyObjectWrapper(PyObject); +# use pyo3::types::{PyDict, IntoPyDict}; +# use std::collections::HashMap; -impl IntoPy for MyPyObjectWrapper { - fn into_py(self, py: Python<'_>) -> PyObject { - self.0 - } -} +struct MyMap(HashMap); -impl ToPyObject for MyPyObjectWrapper { - fn to_object(&self, py: Python<'_>) -> PyObject { - self.0.clone_ref(py) +impl IntoPyDict for MyMap +where + K: ToPyObject, + V: ToPyObject, +{ + fn into_py_dict_bound(self, py: Python<'_>) -> Bound<'_, PyDict> { + let dict = PyDict::new_bound(py); + for (key, value) in self.0 { + dict.set_item(key, value) + .expect("Failed to set_item on dict"); + } + dict } } ``` After: + ```rust # use pyo3::prelude::*; -# #[allow(dead_code)] -# struct MyPyObjectWrapper(PyObject); - -impl<'py> IntoPyObject<'py> for MyPyObjectWrapper { - type Target = PyAny; // the Python type - type Output = Bound<'py, Self::Target>; // in most cases this will be `Bound` - type Error = std::convert::Infallible; - - fn into_pyobject(self, py: Python<'py>) -> Result { - Ok(self.0.into_bound(py)) - } -} +# use pyo3::types::{PyDict, IntoPyDict}; +# use std::collections::HashMap; -// `ToPyObject` implementations should be converted to implementations on reference types -impl<'a, 'py> IntoPyObject<'py> for &'a MyPyObjectWrapper { - type Target = PyAny; - type Output = Borrowed<'a, 'py, Self::Target>; // `Borrowed` can be used to optimized reference counting - type Error = std::convert::Infallible; +# #[allow(dead_code)] +struct MyMap(HashMap); - fn into_pyobject(self, py: Python<'py>) -> Result { - Ok(self.0.bind_borrowed(py)) +impl<'py, K, V> IntoPyDict<'py> for MyMap +where + K: IntoPyObject<'py>, + V: IntoPyObject<'py>, +{ + fn into_py_dict(self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new(py); + for (key, value) in self.0 { + dict.set_item(key, value)?; + } + Ok(dict) } } ```
-### Free-threaded Python Support - -PyO3 0.23 introduces preliminary support for the new free-threaded build of -CPython 3.13. PyO3 features that implicitly assumed the existence of the GIL are -not exposed in the free-threaded build, since they are no longer safe. Other -features, such as `GILOnceCell`, have been internally rewritten to be threadsafe -without the GIL, although note that `GILOnceCell` is inherently racey. You can -also use `OnceExt::call_once_py_attached` or -`OnceExt::call_once_force_py_attached` to enable use of `std::sync::Once` in -code that has the GIL acquired without risking a dealock with the GIL. We plan -We plan to expose more extension traits in the future that make it easier to -write code for the GIL-enabled and free-threaded builds of Python. - -If you make use of these features then you will need to account for the -unavailability of this API in the free-threaded build. One way to handle it is -via conditional compilation -- extensions built for the free-threaded build will -have the `Py_GIL_DISABLED` attribute defined. - -See [the guide section on free-threaded Python](free-threading.md) for more -details about supporting free-threaded Python in your PyO3 extensions. - ## from 0.21.* to 0.22 ### Deprecation of `gil-refs` feature continues -
+
Click to expand Following the introduction of the "Bound" API in PyO3 0.21 and the planned removal of the "GIL Refs" API, all functionality related to GIL Refs is now gated behind the `gil-refs` feature and emits a deprecation warning on use. @@ -257,7 +271,7 @@ See the 0.21 migration entry for help upgrading.
### Deprecation of implicit default for trailing optional arguments -
+
Click to expand With `pyo3` 0.22 the implicit `None` default for trailing `Option` type argument is deprecated. To migrate, place a `#[pyo3(signature = (...))]` attribute on affected functions or methods and specify the desired behavior. @@ -289,7 +303,7 @@ fn increment(x: u64, amount: Option) -> u64 {
### `Py::clone` is now gated behind the `py-clone` feature -
+
Click to expand If you rely on `impl Clone for Py` to fulfil trait requirements imposed by existing Rust code written without PyO3-based code in mind, the newly introduced feature `py-clone` must be enabled. @@ -301,7 +315,7 @@ Related to this, we also added a `pyo3_disable_reference_pool` conditional compi
### Require explicit opt-in for comparison for simple enums -
+
Click to expand With `pyo3` 0.22 the new `#[pyo3(eq)]` options allows automatic implementation of Python equality using Rust's `PartialEq`. Previously simple enums automatically implemented equality in terms of their discriminants. To make PyO3 more consistent, this automatic equality implementation is deprecated in favour of having opt-ins for all `#[pyclass]` types. Similarly, simple enums supported comparison with integers, which is not covered by Rust's `PartialEq` derive, so has been split out into the `#[pyo3(eq_int)]` attribute. @@ -335,7 +349,7 @@ enum SimpleEnum {
### `PyType::name` reworked to better match Python `__name__` -
+
Click to expand This function previously would try to read directly from Python type objects' C API field (`tp_name`), in which case it diff --git a/src/instance.rs b/src/instance.rs index 99643e12eb1..14d2d11b5d7 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1059,7 +1059,7 @@ impl<'a, 'py, T> BoundObject<'py, T> for Borrowed<'a, 'py, T> { /// /// # A note on `Send` and `Sync` /// -/// Accessing this object is threadsafe, since any access to its API requires a [`Python<'py>`](crate::Python) token. +/// Accessing this object is thread-safe, since any access to its API requires a [`Python<'py>`](crate::Python) token. /// As you can only get this by acquiring the GIL, `Py<...>` implements [`Send`] and [`Sync`]. /// /// [`Rc`]: std::rc::Rc diff --git a/src/lib.rs b/src/lib.rs index c71e12b8649..265824adab1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -506,6 +506,7 @@ pub mod doc_test { "guide/src/class/object.md" => guide_class_object, "guide/src/class/numeric.md" => guide_class_numeric, "guide/src/class/protocols.md" => guide_class_protocols_md, + "guide/src/class/thread-safety.md" => guide_class_thread_safety_md, "guide/src/conversions.md" => guide_conversions_md, "guide/src/conversions/tables.md" => guide_conversions_tables_md, "guide/src/conversions/traits.md" => guide_conversions_traits_md, diff --git a/src/marker.rs b/src/marker.rs index 1a4c0482569..5962b47b60b 100644 --- a/src/marker.rs +++ b/src/marker.rs @@ -1,6 +1,6 @@ //! Fundamental properties of objects tied to the Python interpreter. //! -//! The Python interpreter is not threadsafe. To protect the Python interpreter in multithreaded +//! The Python interpreter is not thread-safe. To protect the Python interpreter in multithreaded //! scenarios there is a global lock, the *global interpreter lock* (hereafter referred to as *GIL*) //! that must be held to safely interact with Python objects. This is why in PyO3 when you acquire //! the GIL you get a [`Python`] marker token that carries the *lifetime* of holding the GIL and all diff --git a/src/pycell.rs b/src/pycell.rs index 51c9f201068..c7e5226a292 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -7,7 +7,7 @@ //! PyO3 deals with these differences by employing the [Interior Mutability] //! pattern. This requires that PyO3 enforces the borrowing rules and it has two mechanisms for //! doing so: -//! - Statically it can enforce threadsafe access with the [`Python<'py>`](crate::Python) token. +//! - Statically it can enforce thread-safe access with the [`Python<'py>`](crate::Python) token. //! All Rust code holding that token, or anything derived from it, can assume that they have //! safe access to the Python interpreter's state. For this reason all the native Python objects //! can be mutated through shared references.