Skip to content

Commit

Permalink
guide: note existence of PyFunction::new_closure
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Aug 23, 2022
1 parent 86a1116 commit f753790
Show file tree
Hide file tree
Showing 2 changed files with 8 additions and 15 deletions.
19 changes: 6 additions & 13 deletions guide/src/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,6 @@ Docstring: This function adds two unsigned 64-bit integers.
Type: builtin_function_or_method
```

### Closures

Currently, there are no conversions between `Fn`s in Rust and callables in Python. This would
definitely be possible and very useful, so contributions are welcome. In the meantime, you can do
the following:

### Calling Python functions in Rust

You can pass Python `def`'d functions and built-in functions to Rust functions [`PyFunction`]
Expand All @@ -217,13 +211,12 @@ with only positional args.

### Calling Rust functions in Python

If you have a static function, you can expose it with `#[pyfunction]` and use [`wrap_pyfunction!`]
to get the corresponding [`PyCFunction`]. For dynamic functions, e.g. lambdas and functions that
were passed as arguments, you must put them in some kind of owned container, e.g. a `Box`.
(A long-term solution will be a special container similar to wasm-bindgen's `Closure`). You can
then use a `#[pyclass]` struct with that container as a field as a way to pass the function over
the FFI barrier. You can even make that class callable with `__call__` so it looks like a function
in Python code.
The ways to convert a Rust function into a Python object vary depending on the function:

- Named functions, e.g. `fn foo()`: add `#[pyfunction]` and then use [`wrap_pyfunction!`] to get the corresponding [`PyCFunction`].
- Anonymous functions (or closures), e.g. `foo: fn()` either:
- use a `#[pyclass]` struct which stores the function as a field and implement `__call__` to call the stored function.
- use `PyFunction::new_closure` to create an object directly from the function.

[`PyAny::is_callable`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#tymethod.is_callable
[`PyAny::call`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#tymethod.call
Expand Down
4 changes: 2 additions & 2 deletions guide/src/function/error_handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,14 @@ fn parse_int(s: String) -> PyResult<usize> {

The Rust compiler will not permit implementation of traits for types outside of the crate where the type is defined. (This is known as the "orphan rule".)

Given a type `OtherError` which is defined in thirdparty code, there are two main strategies available to integrate it with PyO3:
Given a type `OtherError` which is defined in third-party code, there are two main strategies available to integrate it with PyO3:

- Create a newtype wrapper, e.g. `MyOtherError`. Then implement `From<MyOtherError> for PyErr` (or `PyErrArguments`), as well as `From<OtherError>` for `MyOtherError`.
- Use Rust's Result combinators such as `map_err` to write code freely to convert `OtherError` into whatever is needed. This requires boilerplate at every usage however gives unlimited flexibility.

To detail the newtype strategy a little further, the key trick is to return `Result<T, MyOtherError>` from the `#[pyfunction]`. This means that PyO3 will make use of `From<MyOtherError> for PyErr` to create Python exceptions while the `#[pyfunction]` implementation can use `?` to convert `OtherError` to `MyOtherError` automatically.

The following example demonstrates this for some imaginary thirdparty crate `some_crate` with a function `get_x` returning `Result<i32, OtherError>`:
The following example demonstrates this for some imaginary third-party crate `some_crate` with a function `get_x` returning `Result<i32, OtherError>`:

```rust
# mod some_crate {
Expand Down

0 comments on commit f753790

Please sign in to comment.