previous
January 1
~ 4 min
~0.6k readers


Python is truly amazing. With all that greatness generally there has to be a tradeoff and in the case of Python it’s performance.

Luckily there is an easy way to run computation intensive work in Rust, which is of course orders of magnitude faster. Let’s see how!

Overview

  1. hello world example
  2. parameters & returns
  3. rust data types compared to pythons ctypes
  4. lists / arrays
  5. complex data types handling
Photo by Jonathan Chng on Unsplash

Lets assume we want to run the following python code in rust.

def add(a, b):
    return a + b

add(1, 2)  # 3

Lets see the steps we need to take to achieve this:

  1. write some rust code
  2. compile rust
  3. import rust in python
  4. run the rust code inside of python

Hello world example

First lets create a new rust project by running:

cargo new rust_in_python

Then lets rename src/main.rs to src/lib.rs as we want a library and not standalone program.

mv src/main.rs src/lib.rs

Now we simply write a hello world function in rust

#[no_mangle]
fn hello() {
    println!("Hello from rust 👋");
}

For every function that need to be available to other languages (in our case Python) through foreign function call (ffi) we will need to add the #[no_mangle] flag to it.

The last step is to tell rust to compile to a dynamic library. To do so simply add the following to your Cargo.toml config file.

[lib]
crate-type = ["dylib"]

Now we are ready to build 🚀

cargo build --release

Now just create a main.py file and we can import and run our function.

from ctypes import CDLL

lib = CDLL("target/release/librust_in_python.dylib")
lib.hello()

And if you run it you will be greeted from rust. No need to install, the ctypes package is included the standard python library.

python main.py

With return & parameters

Of course without giving parameters to the function and receiving its output this whole endeavour would be useless.

Before we start I would like to remind you that python is untyped whereas rust of course is strongly typed. This means that we will need to tell python what types the parameters and the return of our rust function we have.

First lets write the simple add function in rust

#[no_mangle]
fn add(a: f64, b: f64) -> f64 {
    return a + b;
}

Don’t forget to build again 😉

cargo build --release

Now to the python part

from ctypes import CDLL, c_double

lib = CDLL("target/release/librust_in_python.dylib")

lib.add.argtypes = (c_double, c_double)
lib.add.restype = c_double

result = lib.add(1.5, 2.5)
print(result)  # 4.0

Lets see what is happening here:

With lib.add.argtypes we must pass a tuple specifying how python should parse the data we pass to the add function. The ctypes package includes a list of different types we can use. See the full list here.

The same happens with lib.add.restype. As you might have guessed this tells python what type is returned from the rust function.

In our case it’s all c_double as we are using f64 in rust.

Data types in rust

Lets see some other data types and their equivalent in rust.

Python C Rust
c_bool
c_byte char i8
c_ubyte unsigned char u8
c_short short i16
c_ushort unsigned short u16
c_int int i32
c_uint unsigned int u32
c_long long i64
c_ulong unsigned long u64
c_float float f32
c_double double f64

Arrays & List

So what about lists? Unfortunately I have not found a way to use Vectors for dynamic size arrays. So for now it’s just fixed size arrays.

Rust
#[no_mangle]
fn sum(arr: [i32; 5]) -> i32 {
    let mut total: i32 = 0;
    for number in arr.iter() {
        total += number;
    }
    return total;
}
Python
from ctypes import CDLL, c_int

lib = CDLL("target/release/librust_in_python.dylib")

lst = [1, 2, 3, 4, 5]
# Create the memory of the list size
seq = c_int * len(lst)
arr = seq(*lst)

result = lib.sum(arr)
print(result)

Classes and complex data types

Often it can be very useful to send and/or receive data in a structured, compact way. We can do this using structs.

Rust
#[repr(C)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

#[no_mangle]
fn greet_point(p: Point) {
    println!("x: {}, y: {}", p.x, p.y);
}
Python
from ctypes import CDLL, Structure, c_double

lib = CDLL("target/release/librust_in_python.dylib")

class Point(Structure):
    _fields_ = [
        ('x', c_double),
        ('y', c_double)
    ]


p = Point(x=1.2, y=3.4)
lib.greet_point(p)






Subscribe for new posts. No spam.

No 3rd parties. Privacy Policy.

next