Bakken & Baeck logo

Converting a project from Python to Rust, piece by piece | Tristan King

At BB we've historically done most of our projects in Python. However, with Rust's growing popularity amongst developers, growing ecosystem and it's potential for improving sustainability of our projects 1, Rust is looking more and more like a good choice for our projects moving forward.

At BB we've been using FastAPI for many of our projects over recent years. FastAPI is an ASGI application framework. That is, FastAPI does not handle any of the lower level HTTP protocol details, instead leaving that up to an ASGI Server, which converts the HTTP requests into ASGI message and passes them onto FastAPI (or whatever other ASGI application framework you're using).

Axum is an up and coming Rust web framework. It uses the Hyper and Tower crates 2 for handling the low level HTTP protocol stuff, and provides a more higher level interface which is comparable to something like FastAPI.

e.g. something like this in FastAPI (Python):

async def create_product(
    payload: CreateProductPayload,
    db: asyncpg.pool.Pool = Depends(get_database)
) -> Product:

looks something like this in Axum (Rust):

async fn create_product(
    Json(payload): Json<CreateProductPayload>,
    Extension(db): Extension<PgPool>,
) -> Result<Json<Product>, ApiError> {

This is especially interesting to us, as it means the concepts for implementing request handlers is somewhat familiar, which makes it easier to onboard people familiar with FastAPI to a Rust project. That is, Axum is an ideal choice for transitioning our team from Python to Rust.

But in addition to trying to start new projects in Rust it may be desirable to transition existing projects from Python to Rust. For most projects, this would be a daunting and time consuming task, as well as being a risk that most projects simply couldn't afford. Luckily, there is still some hope!

PyO3 is a project that provides Rust bindings for Python and tools for easily creating Python modules written in Rust. Essentially enabling us to write rust code, that can be built into a Python module and called from our existing Python code. This is not so useful on it's own though. We could build utility functions and such, but most of our projects are web api servers, and it's not possible (or at least not easy) to write a FastAPI route handler in rust and have it integrated seamlessly into the rest of the FastAPI architecture.

However, as FastAPI is simply an ASGI application which depends on something else to provide the low level HTTP protocol stuff, what if we could run an Axum web server to handle requests that we build in pure rust, but have it fall back onto some code that provides the ASGI server part and using PyO3 build the ASGI messages from the requests and forward them onto our existing FastAPI code?

The ASGI specification isn't an overly complicated specification. And since we have Axum (or Tower) handle the HTTP protocol stuff, it's only a matter of implementing an Axum Handler and converting the Request to the relevant ASGI messages, waiting for the ASGI responses and converting them back into an Axum/Tower Response. Simple!

Well ok, maybe that sounds like a bit of a daughting task for someone without much experience with Rust. That's why we made a thing! Parviocula provides an Axum Handler, and a helper function that wraps an ASGI application. You can check the examples in the project to see how this works.

With this we can start building new parts or rewriting existing parts of our Python application in Rust, while falling back onto our existing Python code for the parts that Rust doesn't yet cover. This lets us start transitioning things to rust piece by piece without having to replace the whole code base in one go.

It's still early days for testing this method, and so far we've only been using it for a proof of concept, but it has shown a lot of promise. We've already tested replacing all usage of Uvicorn 3 with Axum, including using it in a large integration test suite, and found no issues so far. So we're now optimistic that we can move from a primarily Python shop to a mixed Rust and Python shop without having the huge risk of going all in on Rust for both new projects and existing projects.

  1. The energy efficiency paper linked in the article is often criticized for not using real world examples of code, so it's likely too early to really say this with any certainty.

  2. The term used for dependencies/packages in the Rust ecosystem.

  3. The Python ASGI server we use for our services.