Kushal Das

FOSS and life. Kushal Das talks here.

kushal76uaid62oup5774umh654scnu5dwzh4u2534qxhcbi4wbab3ad.onion

Writing Python module in Rust using PyO3

Back in 2017, I wrote about how to create Python modules using Rust. Things changed in between, especially PyO3 project now makes it super easy to create such extension modules.

Requirements

I am using Python 3.7.3 and using the latest nightly build of Rust using rustup tool. Remember to use the nightly toolchain than stable.

rustup default nightly

The example source

use pyo3::prelude::*;
use pyo3::types::PyDict;
use pyo3::wrap_pyfunction;
use std::collections::HashMap;

#[pyfunction]
/// Formats the sum of two numbers as string.
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

#[pyfunction]
/// Formats the sum of two numbers as string.
fn get_result() -> PyResult<HashMap<String, String>> {
    let mut result = HashMap::new();
    result.insert("name".to_string(), "kushal".to_string());
    result.insert("age".to_string(), "36".to_string());
    Ok(result)
}

#[pyfunction]
// Returns a Person class, takes a dict with {"name": "age", "age": 100} format.
fn give_me_a_person(data: &PyDict) -> PyResult<Person> {
    let name: String = data.get_item("name").unwrap().extract().unwrap();
    let age: i64 = data.get_item("age").unwrap().extract().unwrap();

    let p: Person = Person::new(name, age);
    Ok(p)
}

#[pyclass]
#[derive(Debug)]
struct Person {
    #[pyo3(get, set)]
    name: String,
    #[pyo3(get, set)]
    age: i64,
}

#[pymethods]
impl Person {
    #[new]
    fn new(name: String, age: i64) -> Self {
        Person { name, age }
    }
}

#[pymodule]
/// A Python module implemented in Rust.
fn myfriendrust(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_wrapped(wrap_pyfunction!(sum_as_string))?;
    m.add_wrapped(wrap_pyfunction!(get_result))?;
    m.add_wrapped(wrap_pyfunction!(give_me_a_person))?;
    m.add_class::<Person>()?;
    Ok(())
}

In this example, we are creating one class Person, and 3 different function. You can checkout the whole source from this git repo.

get_result function returns a HashMap object from Rust. Where as give_me_person takes a Python dictionary as argument and then creates a Person class from it and then returns it to Python.

One thing to notice that we are using __new__ to create an instance of the Person class. You can read about the class creation in the documentation.

Building the module

$ cargo build --release
<omitting output>
Compiling myfriendrust v0.1.0 (/home/kdas/code/rust/myfriendrust)
Finished release [optimized] target(s) in 2.60s

$ ls target/release/
build deps examples incremental libmyfriendrust.d libmyfriendrust.so

This will create a libmodulename.so file, in this case the name of the file is libmyfriendrust.so. I have a helper shell script try.sh to rename the file to myfriendrust.so so that I can test it.

cd target/release
mv libmyfriendrust.so myfriendrust.so
python3

Now, let us try it out.

To build a wheel for distribution

You can use maturin tool to create a wheel for distribution.

$ maturin build
🔗 Found pyo3 bindings
🐍 Found CPython 3.7m at python3.7
Compiling proc-macro2 v1.0.10
Compiling unicode-xid v0.2.0
Compiling syn v1.0.18
Compiling serde v1.0.106
Compiling ryu v1.0.4
Compiling proc-macro-hack v0.5.15
Compiling libc v0.2.69
Compiling regex-syntax v0.6.17
Compiling autocfg v1.0.0
Compiling itoa v0.4.5
Compiling scopeguard v1.1.0
Compiling smallvec v1.4.0
Compiling cfg-if v0.1.10
Compiling unindent v0.1.5
Compiling inventory v0.1.6
Compiling version_check v0.9.1
Compiling num-traits v0.2.11
Compiling lock_api v0.3.4
Compiling quote v1.0.3
Compiling regex v1.3.7
Compiling parking_lot_core v0.7.2
Compiling parking_lot v0.10.2
Compiling pyo3-derive-backend v0.9.2
Compiling serde_derive v1.0.106
Compiling ghost v0.1.1
Compiling ctor v0.1.14
Compiling paste-impl v0.1.11
Compiling inventory-impl v0.1.6
Compiling indoc-impl v0.3.5
Compiling paste v0.1.11
Compiling pyo3cls v0.9.2
Compiling indoc v0.3.5
Compiling serde_json v1.0.51
Compiling pyo3 v0.9.2
Compiling myfriendrust v0.1.0 (/home/kdas/code/rust/myfriendrust)
Finished dev [unoptimized + debuginfo] target(s) in 56.57s
📦 Built wheel for CPython 3.7m to /home/kdas/code/rust/myfriendrust/target/wheels/myfriendrust-0.1.0-cp37-cp37m-manylinux1_x86_64.whl

This post is just an introduction. You can look into the documentation and start writing more complex code. If you say I write bad Rust code, then yes. I am still a very beginner in Rust.