DEV Community

Alexander Przemysław Kamiński
Alexander Przemysław Kamiński

Posted on • Originally published at xlii.space on

The Four-Language Waltz: A Tale of Allocators and Regret

The Definition of Insanity

They say the definition of insanity is doing the same thing over and over again and expecting different results. In software engineering, we call this "benchmarking." Or, in my specific case, "rewriting a template utility four times because I am spiritually incapable of settling."

The goal was simple. Deceptively so. I wanted a tool called tmplr. Its job? To take a template, sprinkle in some variables, and spit out a file. It is the sort of task that a shell script could do if you didn't value your sanity, or that Python could do if you didn't value your startup time.

I wanted it to be like Haskell's Stack Templates—human-readable, elegant—but without the requirement that the output be a valid Cabal project. I wanted the freedom to template anything. A grocery list. A love letter. A ransom note. The possibilities were endless.

So, naturally, I decided to write it in every language I could get my hands on until one of them didn't make me cry.

Attempt 1: Haskell (The Mathematical Elephant)

I started with Haskell. It was a smooth ride. Haskell is like a very expensive, very comfortable armchair that you can never actually move out of the room.

The code flowed. The types aligned like the stars in a favorable horoscope. But then came the compilation.

I wanted this tool to be shareable. Portable. I wanted binaries. Haskell looked at me, adjusted its monocle, and handed me a 30MiB executable. Thirty. Megabytes. For a text replacement tool. That is not a utility; that is a piece of furniture.

And cross-compilation? Forget it. I had a binary for Linux Debian. If you were on Windows or MacOS, you were essentially dead to me. Haskell is a language of pure thought, and apparently, pure thought is very heavy to carry around on a USB drive.

Attempt 2: Go (The Beige Cardigan)

I fled to Go. Go is the language you use when you want to get work done and you don't care if you feel anything while doing it.

I had just written a stream sorter (ssort) with the help of an LLM, and the experience was... fine. It was "boring productive."1.

The binaries were slimmer. The cross-compilation actually worked.

But I missed the types. Oh, how I missed them. Going from Haskell to Go is like going from playing 4D chess to playing Checkers with rocks. It works, but you spend a lot of time checking if rock != nil. I yearned for structure. I yearned for the compiler to judge me more harshly.

Attempt 3: Zig (The Icarus Maneuver)

And so, I asked the oracle (the LLM) for a middle ground. It suggested Rust.

Naturally, I ignored it and picked Zig.

Zig is seductive. It promises you the control of C without the decades of psychological trauma. It promises binaries so small they could fit in a text message. For a while, it was paradise.

Then I hit The Segmentation Function.

This was the core logic: taking the template, splitting it, managing the variables, and dealing with branching logic. It was a complex, 200-line beast. I wrestled it into submission. It worked!

But then, the Debug Allocator cleared its throat.

“Excuse me,” it whispered, “but I believe you have leaked memory.”

I had forgotten to release intermediate objects. I stared at the leak report. The leak report stared back. My head began to throb in that specific way that suggests your brain is trying to physically reject the concept of manual memory management.

I looked at the code. I looked at the headache medicine. I chose life.

Attempt 4: Rust (The Strict Librarian)

I crawled back to Rust.

I don't love Rust. I don't wear the socks. I don't attend the crab-themed parties. But at that moment, I needed a babysitter. I needed a compiler that would grab me by the wrist and say, “No, Alexander. You cannot drop that reference there. Pick it up.”

I rewrote the Segmentation Function. It was still difficult. I still had logic errors. But here, the types and the robust testing framework acted as guard rails. When I made a mistake, the compiler didn't just segfault and laugh; it gave me an essay on why I was wrong.

The Verdict

Is it the Holy Grail?

No. The binary is 350KiB. I wanted under 100KiB. But compared to the 30MiB Haskell Leviathan, it is a feather.

It is fast. It is correct. It runs on Linux. It does not run on Windows, because including pathing libraries would add bloat, and I have principles2.

I have come to an understanding with Rust. I don't have to be a fanboy to appreciate that sometimes, it’s nice to have a tool that stops you from shooting your own foot off, even if it forces you to ask permission before loading the gun.

It works. And I am never rewriting it again. Probably.


  1. "Boring Productive" is the highest compliment you can pay a language in a corporate environment, and the worst insult you can pay it on a weekend. 

  2. Also, Windows paths use backslashes, which are clearly an abomination against god and nature. 

Top comments (0)