Arguments

no_op, the function you added to solve the previous exercise, is very simple:

#![allow(unused)]
fn main() {
use pyo3::prelude::*;
    
#[pyfunction]
fn no_op() {
    // Do nothing
}
}

Let's take it up a notch: what if you want to pass a value from Python to Rust?

The FromPyObject trait

#[pyfunction] functions can take arguments, just like regular Rust functions.
But there's a catch: it must be possible to build those arguments from Python objects.

The contract is encoded in the FromPyObject trait, defined in pyo3:

#![allow(unused)]
fn main() {
pub trait FromPyObject<'py>: Sized {
    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self>;
}
}

We won't go into the details of FromPyObject's definition just yet: it would require an in-depth discussion of Python's Global Interpreter Lock (GIL) and the way pyo3 models it in Rust. We'll get to it in the next section.
For the time being, let's focus on what the trait unlocks for us: the ability to convert Python objects into Rust types.

Available implementations

pyo3 provides implementations of FromPyObject for a large number of types—e.g. i32, f64, String, Vec, etc. You can find an exhaustive list in pyo3's guide, under the "Rust" table column.

Conversion cost

Going from a Python object to a Rust type is not free—e.g. the in-memory representation of a Python list doesn't match the in-memory representation of a Rust Vec.
The conversion introduces a (usually small) overhead that you'll have to incur every time you invoke your Rust function from Python. It's a good trade-off if you end up performing enough computational work in Rust to amortize the conversion cost.

Python-native types

In pyo3's documentation you can see a column of "Python-native" types.
Don't try to use them to solve the exercise for this section: we'll cover them in the next one.

References

Exercise

The exercise for this section is located in 01_intro/04_arguments