Skip to content

Commit

Permalink
docs: updates to guide for PyO3 0.21 feedback (#4031)
Browse files Browse the repository at this point in the history
* docs: add notes on smart pointer conversions

* guide: add more notes on `.extract::<&str>()` to migration guide
  • Loading branch information
davidhewitt authored Apr 1, 2024
1 parent 8f87b86 commit 8cabd26
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 1 deletion.
16 changes: 15 additions & 1 deletion guide/src/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,14 +246,26 @@ Because the new `Bound<T>` API brings ownership out of the PyO3 framework and in
- `Bound<PyTuple>::iter_borrowed` is slightly more efficient than `Bound<PyTuple>::iter`. The default iteration of `Bound<PyTuple>` cannot return borrowed references because Rust does not (yet) have "lending iterators". Similarly `Bound<PyTuple>::get_borrowed_item` is more efficient than `Bound<PyTuple>::get_item` for the same reason.
- `&Bound<T>` does not implement `FromPyObject` (although it might be possible to do this in the future once the GIL Refs API is completely removed). Use `bound_any.downcast::<T>()` instead of `bound_any.extract::<&Bound<T>>()`.
- `Bound<PyString>::to_str` now borrows from the `Bound<PyString>` rather than from the `'py` lifetime, so code will need to store the smart pointer as a value in some cases where previously `&PyString` was just used as a temporary. (There are some more details relating to this in [the section below](#deactivating-the-gil-refs-feature).)
- `.extract::<&str>()` now borrows from the source Python object. The simplest way to update is to change to `.extract::<PyBackedStr>()`, which retains ownership of the Python reference. See more information [in the section on deactivating the `gil-refs` feature](#deactivating-the-gil-refs-feature).

To convert between `&PyAny` and `&Bound<PyAny>` you can use the `as_borrowed()` method:
To convert between `&PyAny` and `&Bound<PyAny>` use the `as_borrowed()` method:

```rust,ignore
let gil_ref: &PyAny = ...;
let bound: &Bound<PyAny> = &gil_ref.as_borrowed();
```

To convert between `Py<T>` and `Bound<T>` use the `bind()` / `into_bound()` methods, and `as_unbound()` / `unbind()` to go back from `Bound<T>` to `Py<T>`.

```rust,ignore
let obj: Py<PyList> = ...;
let bound: &Bound<'py, PyList> = obj.bind(py);
let bound: Bound<'py, PyList> = obj.into_bound(py);
let obj: &Py<PyList> = bound.as_unbound();
let obj: Py<PyList> = bound.unbind();
```

<div class="warning">

⚠️ Warning: dangling pointer trap 💣
Expand Down Expand Up @@ -325,6 +337,8 @@ There is just one case of code that changes upon disabling these features: `From

To make PyO3's core functionality continue to work while the GIL Refs API is in the process of being removed, disabling the `gil-refs` feature moves the implementations of `FromPyObject` for `&str`, `Cow<'_, str>`, `&[u8]`, `Cow<'_, u8>` to a new temporary trait `FromPyObjectBound`. This trait is the expected future form of `FromPyObject` and has an additional lifetime `'a` to enable these types to borrow data from Python objects.

PyO3 0.21 has introduced the [`PyBackedStr`]({{#PYO3_DOCS_URL}}/pyo3/pybacked/struct.PyBackedStr.html) and [`PyBackedBytes`]({{#PYO3_DOCS_URL}}/pyo3/pybacked/struct.PyBackedBytes.html) types to help with this case. The easiest way to avoid lifetime challenges from extracting `&str` is to use these. For more complex types like `Vec<&str>`, is now impossible to extract directly from a Python object and `Vec<PyBackedStr>` is the recommended upgrade path.

A key thing to note here is because extracting to these types now ties them to the input lifetime, some extremely common patterns may need to be split into multiple Rust lines. For example, the following snippet of calling `.extract::<&str>()` directly on the result of `.getattr()` needs to be adjusted when deactivating the `gil-refs-migration` feature.

Before:
Expand Down
39 changes: 39 additions & 0 deletions guide/src/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,45 @@ for i in 0..=2 {
# Python::with_gil(example).unwrap();
```

### Casting between smart pointer types

To convert between `Py<T>` and `Bound<'py, T>` use the `bind()` / `into_bound()` methods. Use the `as_unbound()` / `unbind()` methods to go back from `Bound<'py, T>` to `Py<T>`.

```rust,ignore
let obj: Py<PyAny> = ...;
let bound: &Bound<'py, PyAny> = obj.bind(py);
let bound: Bound<'py, PyAny> = obj.into_bound(py);
let obj: &Py<PyAny> = bound.as_unbound();
let obj: Py<PyAny> = bound.unbind();
```

To convert between `Bound<'py, T>` and `Borrowed<'a, 'py, T>` use the `as_borrowed()` method. `Borrowed<'a, 'py, T>` has a deref coercion to `Bound<'py, T>`. Use the `to_owned()` method to increment the Python reference count and to create a new `Bound<'py, T>` from the `Borrowed<'a, 'py, T>`.

```rust,ignore
let bound: Bound<'py, PyAny> = ...;
let borrowed: Borrowed<'_, 'py, PyAny> = bound.as_borrowed();
// deref coercion
let bound: &Bound<'py, PyAny> = &borrowed;
// create a new Bound by increase the Python reference count
let bound: Bound<'py, PyAny> = borrowed.to_owned();
```

To convert between `Py<T>` and `Borrowed<'a, 'py, T>` use the `bind_borrowed()` method. Use either `as_unbound()` or `.to_owned().unbind()` to go back to `Py<T>` from `Borrowed<'a, 'py, T>`, via `Bound<'py, T>`.

```rust,ignore
let obj: Py<PyAny> = ...;
let borrowed: Borrowed<'_, 'py, PyAny> = bound.as_borrowed();
// via deref coercion to Bound and then using Bound::as_unbound
let obj: &Py<PyAny> = borrowed.as_unbound();
// via a new Bound by increasing the Python reference count, and unbind it
let obj: Py<PyAny> = borrowed.to_owned().unbind().
```

## Concrete Python types

In all of `Py<T>`, `Bound<'py, T>`, and `Borrowed<'a, 'py, T>`, the type parameter `T` denotes the type of the Python object referred to by the smart pointer.
Expand Down

0 comments on commit 8cabd26

Please sign in to comment.