Skip to content

Commit a9dff83

Browse files
Upgrade to [email protected] (#10)
1 parent 685ee04 commit a9dff83

File tree

25 files changed

+147
-81
lines changed

25 files changed

+147
-81
lines changed

Cargo.lock

Lines changed: 48 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
[workspace]
22
members = ["exercises/*/*", "patcher"]
33
resolver = "2"
4+
5+
[workspace.dependencies]
6+
anyhow = "1"
7+
pyo3 = "0.23.3"
8+
semver = "1.0.23"
9+
serde = "1.0.204"
10+
serde_json = "1.0.120"

book/src/01_intro/01_setup.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ name = "setup"
6161
crate-type = ["cdylib"]
6262

6363
[dependencies]
64-
pyo3 = "0.21.1"
64+
pyo3 = "0.23.0"
6565
```
6666

6767
Two things stand out in this file compared to a regular Rust project:

book/src/01_intro/04_arguments.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,4 @@ Don't try to use them to solve the exercise for this section: we'll cover them i
5353

5454
## References
5555

56-
- [The `FromPyObject` trait](https://docs.rs/pyo3/0.22.0/pyo3/conversion/trait.FromPyObject.html)
56+
- [The `FromPyObject` trait](https://docs.rs/pyo3/0.23.3/pyo3/conversion/trait.FromPyObject.html)

book/src/01_intro/05_gil.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ when we're interacting with the Python object during the conversion.
134134

135135
## References
136136

137-
- [`FromPyObject`](https://docs.rs/pyo3/0.22.1/pyo3/conversion/trait.FromPyObject.html)
138-
- [`Python<'py>`](https://docs.rs/pyo3/0.22.1/pyo3/marker/struct.Python.html)
137+
- [`FromPyObject`](https://docs.rs/pyo3/0.23.3/pyo3/conversion/trait.FromPyObject.html)
138+
- [`Python<'py>`](https://docs.rs/pyo3/0.23.3/pyo3/marker/struct.Python.html)
139139
- [Global Interpreter Lock](https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock)
140140
- [Official guidance on Python-native vs Rust-native types](https://pyo3.rs/v0.22.0/conversions/tables#using-rust-library-types-vs-python-native-types)
141141

book/src/01_intro/06_output.md

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,98 @@
33
We've gone deep into the weeds of how `pyo3` handles arguments to your `#[pyfunction]`s.
44
Let's now move our focus to output values: how do you return _something_ from your Rust functions to Python?
55

6-
## `IntoPy`
6+
## `IntoPyObject`
77

88
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:
1010

1111
```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>;
1418
}
1519
```
1620

17-
The output type of your `#[pyfunction]` must implement `IntoPy`.
21+
The output type of your `#[pyfunction]` must implement `IntoPyObject`.
1822

19-
### `IntoPy::into_py`
23+
### `IntoPyObject::into_pyobject`
2024

21-
`IntoPy::into_py` expects two arguments:
25+
`IntoPyObject::into_pyobject` expects two arguments:
2226

2327
- `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.
2532

2633
## Case study: a newtype
2734

2835
Let's look at a simple example: a newtype that wraps a `u64`.
2936
We want it to be represented as a "plain" integer in Python.
3037

3138
```rust
39+
use std::convert::Infallible;
3240
use pyo3::prelude::*;
41+
use pyo3::types::PyInt;
3342

3443
struct MyType {
3544
value: u64,
3645
}
3746

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)
4169
}
4270
}
4371
```
4472

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+
4590
## Provided implementations
4691

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`.

book/src/02_classes/00_pyclass.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
3131
}
3232
```
3333

34-
## `IntoPy`
34+
## `IntoPyObject`
3535

36-
Rust types that have been annotated with `#[pyclass]` automatically implement the `IntoPy` trait, thus
36+
Rust types that have been annotated with `#[pyclass]` automatically implement the `IntoPyObject` trait, thus
3737
allowing you to return them from your `#[pyfunction]`s.
3838

3939
For example, you can define a function that creates a new `Wallet` instance:

0 commit comments

Comments
 (0)