12/25/2024, 7:05:03 PM
It’s been almost 10 years since I’ve been building webapps one way or the other, all the way back when RoR was all the rage, Laravel had just launched version 4 with a big rewrite, PHP had finally launched it’s highly anticipated v7, jQuery was the boss, Bootstrap was the talk of the town and React was some internal Facebook tool no one knew about.
A lot has changed since those days. The almighty Web Application has eaten the world, leaving behind massive codebases written in scripting languages and deployed on the infinite cloud with lovecraftian tools in its wake (AWS Fargate sounds like an entrypoint to R’leyh to be honest).
React launched and redefined what it meant to write user interfaces on the web, then relaunched with hooks, then relaunched again with server components, inspiring a dozen frameworks and launching a thousand libraries along the way.
Javascript went from a scripting language to a full-blown multistep compiled behemoth with a slew of compiler toolchains (yes, entire toolchains with compilers stacked on top of each other) fighting for bragging rights and package management issues with more holes than the finest swiss cheese.
M$ went from being the devil hatching evil plots against open source to being the best thing for open source (allegedly, I do not believe this psyop one bit) since Sun, all with the help of a code editor written in javascript that requires more memory than an entire PC back in the day of Pentiums and VB6 (that’s a rant for another day).
Laravel and RoR evolved from basic backend frameworks to complete full stack frameworks with features for managing everything from dev environments to deployments.
One thing that remained constant however was the widespread use of untyped REPL based scripting languages.
My first experience with the REPL was with PHP in college. Having written only C and C# till then, writing PHP where you just typed your code and ran it instantly on your browser felt revolutionary to me. I was hooked. No more having to run a compile command after changing your code and breaking your flow in the process.
But it did not last for long. The promise of directly writing code instead of dealing with all the messy data types and thinking about your data structures upfront felt liberating at first, but as the codebases grew, so did the complexity, and it wasn’t long before I found myself running a dozen evals while debugging simply to understand what the actual data structure was, especially when working on multiple massive codebases and context switching between them.
Modifying code was an even bigger challenge. With no exact knowledge of what data structure was being passed through your libraries and frameworks and what exactly was the structure of the data in any given variable at any given point of time, writing code simply became a guessing game where I was making educated guesses and then testing the code after each line just to see if my guess was correct.
This was not scalable, and more than a few times I did slip up and let undefined value
errors go into production, especially when dealing with complex data structures involving the Billion Dollar Mistake.
The Read-Eval-Print loop with scripting languages that used to feel so rewarding was feeling more and more like a study in frustration.
There were attempts to bolt on types, but a lot of them were optional like Sorbet or half-baked attempts like PHP did, with types that were basic but left complex data structures out. Typescript did solve this to some extent, but effectively turned Javascript into a compiled language without a lot of the upsides of one.
While I was frustrated with these issues, the alternatives did not appeal much to me. Java required heavy ide’s like Eclipse to be able to wrangle toolchains and project structures, and the web frameworks were ENTERPRISE with the full weight of the word. Hibernate still is the worst library I have ever worked with, and I worked with PHP professionally for a half a decade.
C# was basically Java that required Windows, and having decamped to macOS and Linux after experiencing Windows 10 I had no desire to go back there.
I did hear and read some things about Rust and webapps, but my mental image of Rust was that of a systems language, with people writing web apps in Rust for fun or as a code challenge or something.
That was until summer this year, when I ran into a project claiming to be RoR, but for Rust. I went into the docs not really convinced, but the more I read, the more it actually looked like the real deal. I didn’t know or understand Rust at that time, but simply going over the docs and the example project gave me the same vibes as I got from Laravel and RoR. I was hooked, so I decided to learn Rust and give it a shot.
(By the way, if you are planning to learn Rust, go straight to the official Rust Book, especially if you already know other languages. It’s straight and direct, unlike a lot of tutorials that assume you are a child and teach you 2+2=4).
So I spent a few weeks learning Rust and then came back with grand plans to rewrite my flat file blog into a full custom blogging engine (sounds a bit cliché, but can you even call yourself a programmer if you haven’t attempted to write at-least one blog engine in your career?).
I instantly felt a clash with how Axum worked (turns out Loco was a wrapper around Axum) around various common and basic scenarios -
tower
.A lot of it felt like the Javascript ecosystem, where you had to integrate dozens of micro packages to make something work, along with the maintenance overhead of such a system that I had felt acutely working with React and Express.
So I decided to write my own framework that would work a more like Laravel and RoR. Everyone suggested using Hyper for handling http, Hyper docs suggested Tokio, so I installed all that and went to the races. But that is where the trouble began.
I started running into errors that the Tokio docs, stackoverflow and AI suggested could be solved with applying combinations of Send, Sync and 'static
and wrapping some things in ARC
and Box
and Pin
or changing the design (which I didn’t want to do). Digging into some codebases like Axum and Viz pointed in the same direction.
But that didn’t seem right to me, especially the ARC
and Send
part. Why would I need all these multi-thread synchronization primitives especially since a request won’t be processed in parallel across multiple threads? Are people conflating concurrency with parallelism?
With some more digging I found out that the whole thing was because Tokio, the runtime that everyone was using under the hood, sets up a work-stealing, multithreaded runtime by default, necessitating these syncing primitives and techniques.
The whole work stealing thing seemed like a bad case of over-engineering and premature optimization, and the more I researched into this, the more I found blogs and articles pointing towards the same thing. I did also find this blog post arguing otherwise,
and this excellent piece from Evan Schwartz about solutions to the Send, Sync, 'static
problem.
It was in that post from Evan that I found out about thread-per-core
runtimes, which led me to Monoio. On paper this looked good. No work stealing, so no multi-threading synchronization required on everything, even if you don’t need it. Performance benchmarks also look very competitive.
A small issue was that all the Hyper examples where written with Tokio, and alternate runtimes are not officially supported as far as I could find, especially something that’s not epoll based. There are workarounds, but it doesn’t feel like a good idea to do that if it might break with updates or cause issues in edge cases.
But since I’m already building a framework, I thought I might as build http request/response handling and everything else from scratch. Would be a fun exercise to flex some rusty neurons (well not truly from scratch, I’ll still be using Monoio as the core and some libraries, especially around parsing and time and databases if I like them, but eh, it’s the sentiment that counts).
So yeah, this project might probably not go anywhere, I might be wrong about Tokio and Hyper and might have bet on the wrong horse with Monoio (I am very new to this Rust thing after all), or I might just run into some wall that I won’t be able to design around. But damn it, I cannot sit around any longer when I still have the ability to do something about my gripes and annoyances. I haven’t felt this excited for writing some code since my college days, and that’s all I need at the end of the day.
Time and experience have taught me that running a compile command does not break your flow. What breaks your flow is doing Guess Driven Development and printing data structures line by line. So keep an eye on this space, and there just might be a better web framework around the bend.