What's your favorite system?

Oxidizing from Scratch

As a personal project and a way to keep my skills sharp between jobs, I’ve developed Kitchen Server – just something to store recipes on a homelab server and serve them as a webpage. As I write this, I just finished the longest single-goal effort I’ve made to it: converting it from Python to Rust.

Why change languages?

Python was the language I was familiar with when I was laid off and started looking for something to do. I know enough C++ to read it, but my magnum opus at Argo AI was written in Python.

What I discovered over several months is that Python is great for moving fast and breaking things. I got a prototype off the ground fairly quickly, and the package repository helped me find libraries to solve problems as I encountered them – things like Jinja for using data to fill in HTML templates, or Flask to actually handle the low-level server functions. However, these libraries gave me two very different experiences.

I started encountering documentation and libraries that I wasn’t entirely sure how to use, partly because typing was non-extant or very loosely implemented. As I built out my own “library” of data structures, I kept making little mistakes that wouldn’t show up until runtime testing. As easy as it was to implement a feature, I had to put in significant work to implement it right. I developed some level of anxiety every time I tried a new part of a library or added another piece of information to my data structures.

Paired with the shortcomings of Python itself – Linux support that’s two minor versions behind, a lack of encapsulation, and enforcement of types falling entirely to the not-included linter to name a couple examples – I realized Python just wasn’t the language I wanted to use for production. I’d stopped trusting my own types if I hadn’t worked on them in the past week, let alone anything I was truly unfamiliar with.

Why Rust?

Some may be asking “Why not Rust in the first place?” Well, the best X is the one you use, right? Python was my language of choice for four months, during which time I got my app off the ground. It took me more than three months to get a first version of the app done in Rust, and I’m confident some new bugs slipped in during the conversion.

The best language for you is the one you’ll use. Don’t beat your head against the wall of Rust’s lifetimes if you’re not confident you’ll stick it through to see the benefits.

Which, to be clear, are numerous. The typing system is something I can’t speak highly enough of. Yes, it’s static and compile-checked, but it still maintains flexibility. I can add my trait to someone else’s struct, or vice versa, and our code is compatible just like that. Even better, I can define the return type of a function by a trait, either statically or dynamically. And as much as I rolled my eyes at having to check every Result and Option, I know without a doubt that my code has thorough error handling, because Rust enforces the handling of all possible enum variants.

After rewriting my server, its source code is notably larger, but I understand phrases like “fearless concurrency”. Rust comes out of the box with compile-time checking, linting, and formatting that give me full confidence that if my code compiles, it will do something like what I intend it to do. I won’t be debugging implicit type conversions or instantiations missing an argument; I’ll be realizing my endpoint is incorrect or doesn’t have the right extractor.

Weirdly enough, a lot of what I said above I can only say in retrospect, and yet that was one of my goals – to learn a new language that everyone seems really interested in. When I considered my options, I was also looking for something compiled with strict types, and I got those. Objectively, my network response times are orders of magnitude faster, and the Docker images are about half the size as the Python versions.

What went wrong?

Every good project needs a post mortem, right?

Let’s see. You could probably scroll through the Rust subreddit and take the highlights of noobie questions. Lifetimes were the big one – I still have some mental to-dos to quit instantiating things for every endpoint when I figure out how to make lifetimes cooperate with Poem’s endpoints. Types were a little more familiar coming from a C++ background, but the way Rust handled them was very different – it took me some time to understand the benefits of impl trait for struct.

Additionally, I didn’t realize how much portability I’d lost with the switch to a compiled program. I assumed – falsely – that Docker provided portability across architectures, which is a thought I never had to challenge when running Python. This seems true to some extent, as my 64-bit Linux AMD target is running fine on my Intel MacBook, so I’ve still got some teasing apart to do there.

The Result

I’ve transformed 500 lines of cross-platform and easily-deployable code into 1500 lines of code that needs to be compiled per architecture, lost some robustness along the way, and postponed any actual feature development for months while I learned a new language.

But I did learn a new language, and quite a few other things. Switching to a compiled language forced me to learn how to manage artifacts in CI so my Docker image could use the binaries built by my Rust image. The minutes-long build cycles also motivated me to implement caching, which reduced my CI usage drastically every time I ran a pipeline. And with standalone binaries, I could build Docker images from scratch. Those images are now about half the size of the old ones, again saving me usage quotas.

Perhaps most importantly for a pet project, I want to keep working on it now. I find Rust a joy to work in because of the structure the language gives me. Yes, it has its sharp edges, but it’s clear. Nothing ate away at my motivation more than running the Python version of my program to test it, only to find that somewhere along the line I’d passed the wrong data structure to a library. In Rust, I know I’m using the language right at a basic level if my program compiles, which gives me the freedom and confidence to focus on my features and implementation.

In fact, while this post has been sitting in my drafts folder, I’ve already released a new subversion of Kitchen Server with an in-browser page creator (editing pending). The back-end code was a breeze as soon as I figured out how to parse the JSON returned from the form on the front-end.

I look forward to the point where I can start asking for non-trivial feedback. With the structure of Rust, I’m confident I’ll stay pointed in the right direction more consistently, and I should hit that level of feature-completion this quarter.

RAW is a WordPress blog theme design inspired by the Brutalist concepts from the homonymous Architectural movement.

Subscribe to our newsletter and receive our very latest news.

Leave a comment