|
3 | 3 | We've gone deep into the weeds of how `pyo3` handles arguments to your `#[pyfunction]`s.
|
4 | 4 | Let's now move our focus to output values: how do you return _something_ from your Rust functions to Python?
|
5 | 5 |
|
6 |
| -## `IntoPy` |
| 6 | +## `IntoPyObject` |
7 | 7 |
|
8 | 8 | Guess what? There's a trait for that too!\
|
9 |
| -`IntoPy` is the counterpart of `FromPyObject`. It converts Rust values into Python objects: |
| 9 | +`IntoPyObject` is the counterpart of `FromPyObject`. It converts Rust values into Python objects: |
10 | 10 |
|
11 | 11 | ```rust
|
12 |
| -pub trait IntoPy<T>: Sized { |
13 |
| - fn into_py(self, py: Python<'_>) -> T; |
| 12 | +pub trait IntoPyObject<'py>: Sized { |
| 13 | + type Target; |
| 14 | + type Output: BoundObject<'py, Self::Target>; |
| 15 | + type Error: Into<PyErr>; |
| 16 | + |
| 17 | + fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error>; |
14 | 18 | }
|
15 | 19 | ```
|
16 | 20 |
|
17 |
| -The output type of your `#[pyfunction]` must implement `IntoPy`. |
| 21 | +The output type of your `#[pyfunction]` must implement `IntoPyObject`. |
18 | 22 |
|
19 |
| -### `IntoPy::into_py` |
| 23 | +### `IntoPyObject::into_pyobject` |
20 | 24 |
|
21 |
| -`IntoPy::into_py` expects two arguments: |
| 25 | +`IntoPyObject::into_pyobject` expects two arguments: |
22 | 26 |
|
23 | 27 | - `self`: the Rust value you want to convert into a Python object.
|
24 |
| -- `Python<'_>`: a GIL token that you can use to create new Python objects. |
| 28 | +- `Python<'py>`: a GIL token that you can use to create new Python objects. |
| 29 | + |
| 30 | +The conversion can fail, so the method returns a `Result`.\ |
| 31 | +The output type itself is more complex, so let's break it down using an example. |
25 | 32 |
|
26 | 33 | ## Case study: a newtype
|
27 | 34 |
|
28 | 35 | Let's look at a simple example: a newtype that wraps a `u64`.
|
29 | 36 | We want it to be represented as a "plain" integer in Python.
|
30 | 37 |
|
31 | 38 | ```rust
|
| 39 | +use std::convert::Infallible; |
32 | 40 | use pyo3::prelude::*;
|
| 41 | +use pyo3::types::PyInt; |
33 | 42 |
|
34 | 43 | struct MyType {
|
35 | 44 | value: u64,
|
36 | 45 | }
|
37 | 46 |
|
38 |
| -impl IntoPy<Py<PyAny>> for MyType { |
39 |
| - fn into_py(self, py: Python<'_>) -> Py<PyAny> { |
40 |
| - self.value.to_object(py) |
| 47 | +impl<'py> IntoPyObject<'py> for MyType { |
| 48 | + /// `Target` is the **concrete** Python type we want to use |
| 49 | + /// to represent our Rust value. |
| 50 | + /// The underlying Rust type is a `u64`, so we'll convert it to a `PyInt`, |
| 51 | + /// a Python integer. |
| 52 | + type Target = PyInt; |
| 53 | + /// `Output`, instead, is a **wrapper** around the concrete type. |
| 54 | + /// It captures the ownership relationship between the Python object |
| 55 | + /// and the Python runtime. |
| 56 | + /// In this case, we're using a `Bound` smart pointer to a `PyInt`. |
| 57 | + /// The `'py` lifetime ensures that the Python object is owned |
| 58 | + /// by the Python runtime. |
| 59 | + type Output = Bound<'py, PyInt>; |
| 60 | + /// Since the conversion can fail, we need to specify an error type. |
| 61 | + /// We can't fail to convert a `u64` into a Python integer, |
| 62 | + /// so we'll use `Infallible` as the error type. |
| 63 | + type Error = Infallible; |
| 64 | + |
| 65 | + fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> { |
| 66 | + // `u64` already implements `IntoPyObject`, so we delegate |
| 67 | + // to its implementation to do the actual conversion. |
| 68 | + self.value.into_pyobject(py) |
41 | 69 | }
|
42 | 70 | }
|
43 | 71 | ```
|
44 | 72 |
|
| 73 | +### The `Output` associated type |
| 74 | + |
| 75 | +Let's focus on the `Output` associated type for a moment.\ |
| 76 | +In almost all cases, you'll be setting `Output` to `Bound<'py, Self::Target>`[^syntax]. You're creating a new Python |
| 77 | +object and its lifetime is tied to the Python runtime. |
| 78 | + |
| 79 | +In a few cases, you might be able to rely on [`Borrowed<'a, 'py, Self::Target>`](https://docs.rs/pyo3/0.23.3/pyo3/prelude/struct.Borrowed.html) |
| 80 | +instead. |
| 81 | +It's slightly faster[^conversation], but it's limited to scenarios where you are borrowing from an existing Python object—fairly |
| 82 | +rare for an `IntoPyObject` implementation. |
| 83 | + |
| 84 | +There are no other options for `Output`, since `Output` must implement |
| 85 | +[the `BoundObject` trait](https://docs.rs/pyo3/0.23.3/pyo3/trait.BoundObject.html), |
| 86 | +the trait is [sealed](https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/) and |
| 87 | +those two types are the only implementors within `pyo3`.\ |
| 88 | +If it helps, think of `Output` as an enum with two variants: `Bound` and `Borrowed`. |
| 89 | + |
45 | 90 | ## Provided implementations
|
46 | 91 |
|
47 |
| -`pyo3` provides out-of-the-box implementations of `IntoPy` for many Rust types, as well as for all `Py*` types. |
48 |
| -Check out [its documentation](https://docs.rs/pyo3/0.22.0/pyo3/conversion/trait.IntoPy.html#) for an exhaustive list. |
| 92 | +`pyo3` provides out-of-the-box implementations of `IntoPyObject` for many Rust types, as well as for all `Py*` types. |
| 93 | +Check out [its documentation](https://docs.rs/pyo3/0.23.3/pyo3/conversion/trait.IntoPyObject.html#foreign-impls) |
| 94 | +for an exhaustive list. |
| 95 | + |
| 96 | +[^syntax]: The actual syntax is a bit more complex: `type Output = Bound<'py, <Self as IntoPyObject<'py>>::Target>>;`. |
| 97 | +We've simplified it for clarity. |
| 98 | + |
| 99 | +[^conversation]: In addition to its documentation, you may find [this issue](https://github.com/PyO3/pyo3/issues/4467) |
| 100 | +useful to understand the trade-offs between `&Bound` and `Borrowed`. |
0 commit comments