As far as I know, the “Brogrammer” meme started at Facebook as a sarcastic joke to test the new Groups feature. Although it began as parody, Brogramming morphed into something much more complicated as it entered the popular culture. This was my first taste of how convoluted the meme culture would become: a culture where irony and the grain of truth embedded in it would become one-and-the-same.
There's a lot to unpack about Brogramming, but the subject of this post is the Brogrammer's lesser-known counterpart, the JavaScript Hipster. Although less fully-realized, the JS hipster was the PBR-sipping, kickball-playing complement to the brew-swigging, dumbbell-lifting Brogrammer. At the time, the JS hipster wasn't even coding in JavaScript anymore, having decamped for the greener pastures of CoffeeScript.
I have always been partial to JavaScript (going back to my days at Laszlo Systems!) but the reason I really identified with the JS hipster was their self-conception as an artiste and their natural affinity for front-end and design. "Real" programmers mock the JS hipster for relying on aesthetics and taste to make technical decisions, in contrast to the brogrammer's steadfast refrain of "practicality."
Although I've had a lot of practicality beaten in to me working on systems being strained by hypergrowth, I never lost my faith that pretty things, especially things that are pretty because they are simple, are the best. So I'm still a hipster at heart, and I still rely on aesthetics to guide my technical intuition. Grab yourself an oatmilk latte and unwrap your scarf and let's disaffectedly discuss our observations of the modern programming environment.
And let's start with JavaScript, because it's come a long way since the function.prototype days. I have always liked JavaScript and I still do. Sure, the JS Hipster is a code poet, but, in my opinion, a little ditty like this absolutely rises to level of verse:
function* naturals(count) { let i=0; while(i < count) yield i++; } let ten_squares = [...naturals(10)].map(x=>x*x);
For a long time, I thought that running JavaScript on the server was an inevitability. I built a whole framework for this before the first public beta of Node.js. My particular solution to this problem was latched to Java, which posed some marketing challenges, and it was quickly eclipsed by Node. However, some of the specific choices in Node and some of the lineage of modern JavaScript VMs made initialization of a JavaScript program too expensive to be practical for scaled server-side deployments, especially on typical server hardware, which depends on multicore. Despite several attempts to run JavaScript on the server at scale, we never quite made this work during my time at Facebook.
Part of the failure here was a lesson I learned the hard way that you can't just wish-away performance concerns. Although 90% of the time, performance is about concurrency and architecture, there are some times when straight throughput or especially startup speed really matters. But for a hipster like me, who really cares about origin stories and aesthetics, there hasn't been a clear choice for server programming. Until recently, I thought the best option for server programming was probably Hack. As a side note, this forced me to confront the truth: that as much as I am a hipster, I am also a brogrammer, and Hack's brutalist process architecture strikes some deep chord in me. Now, every hipster is also a contrarian, so I'm used to being sneered at. But Hack isn't a realistic choice for most engineering teams because it's not widely adopted. And those dollar $ign$ everywhere do offend my delicate sensibilities.
For a while, it looked like the hipster consensus was drifting towards Kotlin on the server. Now, I like Kotlin a lot, and I am amazed at how well it interoperates with Java. It's a startling counterpoint to all the ways in which Swift missed maybe its most important requirement, easy interop with Objective-C. But, while I like the language, I'm not a fan of the environment. My experience has taught me to be wary of scaled Java deployments. The JVM is just too big and too abstract, and its integration with the filesystem sucks. It's not really interoperable with anything and JNI is a performance-constrained nightmare. Every Java project ends up with a zillion environment variables and config files and classpath crap that just offends my minimalist sensibilities. Finally, as much as I like garbage collection in JS, and as much as the Java garbage collector helps the v1 team get going quickly, every sufficiently scaled Java program I've ever worked on has ended up with some kind of custom slab allocator to work around the garbage collector.
Enter this the relatively new option of Rust. Rust has the compelling advantage of a minimal runtime, which makes it highly embeddable. Because it makes memory lifetime a first class language feature, it is easy to invoke in both directions through a foreign function interface. It's also got a stellar type system which would be brutally strict if it weren't complemented by a killer macro feature. Not only does this make it easier to log to the console than in Python(!) it also makes it easy to statically initialize data. I think this is an important language feature because making programs data-driven is a favorite tactic of mine in JavaScript, in-part because JavaScript literal syntax is obviously (I mean everyone adopted JSON, right?) superior.
Maybe the best thing about Rust is that, because of its embedability, it can be compiled to WebAssembly and interoperated with, you guessed it, JavaScript. Now, if I had an existing codebase for a successful app, I'd just suck it up and invest in whatever that app happened to be written in. But I'm in the liberated and liberating position of not having to worry about that right now. So I was curious about what I can do with the combination of my old favorite, JavaScript and my current crush, Rust.
To start out, I wanted to do a little project to test out how easy or hard it is to use these technologies in the context of a web-delivered app. For a long time, I've been fascinated by the Mandelbrot Set. Perhaps I will expand on some of why I find this particular bit of math so interesting and exciting in a future post. For the purposes of this project, though, it is just a convenient example of something that has non-trivial computation cost. So I built a little Mandelbrot Set viewer. It's only a sketch. It is rife with bugs and rendering glitches and it only runs in late versions of Chrome. I found some bugs in Chrome's wasm implementation (like hangs!) that make me reluctant to host a version of the app. But if you want to play with it, you can build it yourself from the GitHub repository, and I also recorded this video of its operation.
I had a lot of fun programming this and I fell in love with JavaScript all over again as I wrote this simple task queue over webworker. In the end, the Rust/wasm integration was the easy part of this project. Learning enough WebGL so that I could use hardware to drive the animated transition and make the rendering concurrent was the hard part. GL is strange, and I wouldn't have gotten anywhere without the amazing resources at WebGLFundamentals. It sure is counter-intuitive to a functional JavaScript programmer that the way you program GL is to set a bunch of global variables and then load a program that is delivered as a string. But it's kind of a nifty coincidence that GL is a good example of the fundamental mechanism underlying every inter-process boundary, which is to communicate via shared memory. What I learned about GL helped me pick up concepts that proved useful in designing my Rust extension, like writing to an output buffer and avoiding heap memory allocation.
My experience with Rust was kind of amazing. I was able to run and debug my program in the excellent CLion IDE but I could also compile it for the browser and run it there. I love how Rust keeps so much of what I love from JavaScript, especially first-class functions, but without the VM overhead. Even with a bunch of Option unwrapping to handle overflow, I think this version of the core Mandelbrot algorithm is quite recognizable.
fn calc_z(cx: Fix, cy: Fix) -> usize { let bx: Fix = Fix::from_num(cx); let by: Fix = Fix::from_num(cy); let clamp: Fix = Fix::from_num(2); let c = Complex::new(bx, by); let mut i = 0; let mut z = Some(c); while i < MAX_ITER { z = iter_z(z, c); if !clamp_norm(z, clamp) { break; } i += 1; } return i;
As I learned more about the Rust type system, I found it wasn't too hard to change the number implementation from double floating points, which were fast but too inaccurate to zoom more than a couple times, to Real numbers, which were way too slow, to fixed precision 128 bit numbers using 123 bits for the fractional part, which allows me to represent values between -16 and 16 (I think.) Even so, among the many bugs and glitches in this implementation is that the scale of the viewport eventually becomes too fine and the screen goes black, as you can see at the end of the video above.
Even though this all worked, it's clear to me that WebAssembly is still wet cement. Chrome appears to support it pretty well, but I didn't bother getting this running in Safari. There are at least two issues with that: Safari has this elaborate incantation for loading wasm, and it also seems like it's not as flexible passing BigNums in and out of JavaScript. Even in Chrome, I found what I think are some bugs. Maybe this is intended, but I was able to hang the browser with bad wasm code like infinite recursion. I also think I stumbled on some wasm caching bugs, which would scare me a little if I were looking to deploy these technologies at scale.
But I'm not. I have the total luxury of tinkering right now and you know hipsters love to tinker. I think this is a pretty bad-ass combination and I would love to see more architecture with a core data/business logic framework that is written in Rust and runs both on the client and the server. I have some specific ideas about the storage for applications like this, but that's fodder for a future project.
And this little experiment convinced me to go further with Rust and to start thinking about how I could embed a Rust library in the context of a JavaScript-based web app. After ten years of working on big software, it's so nice to make little things that are only really intended to run on my computer. You can sneer at it, but the hipster ideas of sourcing your ingredients, stripping things down, and paying attention to the aesthetics are what I love about programming.
And it seems to me like these days a lot of the JavaScript world has ditched the hipster aesthetic. When I was figuring out how I wanted to develop this blog, I looked past all of these elaborate static site generation frameworks and went with raw HTML on a bare AWS instance. My staging server is a Raspberry Pi. Rust seems like it fits right in with this mood. I want to know how my DNS is sourced and whether my memory is locally grown.