Web Assembly (WASM) and Rust have been growing and evolving over the last couple of years, so what’s it like to use them together?
I’ve wanted to use Rust and WASM for a while due to a number of reasons: small bundle size, low-level access with reliable performance, and all the perks that come with Rust (strong type safety, zero-cost abstractions, etc.). So, when I was presented with the opportunity of 2 weeks off-project learning, there was no excuse not to give Rust and WASM a try!
What followed over the next 2 weeks or so was a bit of a programming rollercoaster for me, something all programmers have been through many times. But when it came time to write my experiences up for this article, I noticed there was a pattern, this experience wasn’t just any rollercoaster…it mapped perfectly to the structure of a Romcom! So, to explain and analyse this not officially supported pairing of a web application bundler and a systems programming language, we will be following the standard 10 part Romcom format, for structure and a bit of comedic relief.
Another reason I wanted to use Rust and WASM is because it was new and shiny, plus it would be nice to hook up the Rust program to a nice web interface. One problem, Rust and WASM is only officially supported with Webpack as a bundler. To me, Webpack was that Ex in a Romcom, it was never a good relationship and we could never make it work. But, it seemed that it may be a necessary evil to reach my goal of making something using my lost love, Rust.
So, I grudgingly start to clone the Rust WASM Webpack template as I’m flashed back to a previous project, watching myself as I battle with Webpack trying to compile a Single Page App (SPA). The list of dependencies growing with every plugin. I spam Ctrl + C, “No there must be something else” I think. And that's when it hits me, “Parcel! I remember it saying something about WASM?” I think as I quickly navigate to the Parcel website, and there it is, this is what I have been looking for, and after a quick
npm install, I’m head over heels.
npm init and
npm install -D parcel-bundler later and we are off to the races. Parcel supports importing .rs files in JS and TS out of the box so in a simple HTML5 boilerplate with a main.js I do just that. The rust file contains a simple function which when given 2 numbers returns their sum, some extra Rust to tell the compiler not to mangle the function name and it’s done! The JS calls this function and displays the output in the DOM, a simple example but this seems to have everything I need…
But, as with most romcoms, a bump in the road pulls the relationship into question. For Rust and Parcel, this issue was returning or accepting strings in functions. No matter what I did, it wouldn’t work and streams of undefined would plague my console. Although there seemed to be a solution, the well supported “wasm_bindgen” package provides a polyfill for things missing between Rust and JS! So, make a Rust project with a cargo.toml, add the wasm_bingen crate, import and run… oh wait. Parcel doesn’t seem to work with wasm_bindgen even with a plugin someone on a GitHub issue cites as the solution...what now?
After a few minutes of frantic Googling and skim reading GitHub issues and various tutorials on blogs I find an alternative package, stdweb. Seems to have most of the functionality of wasm_bindgen and a handy tutorial with how to set it up with Parcel! A quick switcheroo of the packages in the cargo.toml, some slight code tweaks and we are back on course with strings being returned and received in this simple app. Time to start making something slightly more complex...a simple genetic algorithm simulator!
Okay so new project, Parcel - installed, Rust module - created, stdweb - installed lets get this show on the road! In my head the idea is simple, make a struct in Rust to represent the Genetic Algorithm Simulation, add some methods to it to get the population or simulate a generation, and then simply instantiate and use it in JS. Can’t be too hard surely (foreshadowing)! Lets just make the struct, seems to be instantiating in JS, lets add some methods onto the struct… wait...what? It seems exporting structs is temperamental at best when using stdweb and parcel am I back to square 1 already?
All seems lost, I’m out of viable Rust packages to try and have a console littered with errors, is there nothing I can do? In a last ditch effort I tried manually compiling the .wasm file myself and importing it but after 5 edits to the Rust file I can already feel this getting tedious… As I crawl through GitHub issue after GitHub issue webpack comes up again and again as the solution, maybe I need to accept defeat and go back.
F*** I’m going to have to use Webpack, I think as I go back to the start and open the Webpack Rust template, defeated.
As the Webpack Rust template repo clones I took to Google one last time, using one of my old searches, hoping for a miracle. Wait, what's this? A GitHub issue about Parcel and WASM_Bindgen which wasn’t there before? The Google search index must have only just found this to be relevant? Hold on, someone has linked a template here for Rust, WASM_Bindgen, and Parcel! Thank the Search Engine Gods the project may be saved!
There it was, under my nose the whole time on the rustwasm GitHub repository. I quickly cloned the repo and followed the set-up instructions and it all worked flawlessly. In the end this search had become a real Cinderella story with the perfect match being found on the stroke of midnight.
So now, time to make something cool with it! I didn’t want to focus too much on the front end and slaving over SCSS making it look nice, so I turned to an old friend: TailWindCSS, a utility-first CSS framework which I have set up with PostCSS and Parcel before. With all that done I build out a simple layout with a side panel for configuring the simulation and a main panel to hold the results of the simulation. After deciding on the look and feel of the page I start to make some TypeScript components for controlling and displaying the simulation.
Finally after getting the site up and running with some mock data from a simple
set_interval I start to hook it up to the WASM. It ends up being remarkably simple, just import the
module object from the Rust projects’
cargo.toml and then all the structs and functions are attached to it! A few little tweaks and testing and what do you know, it's all working and converging! A little bit of cleanup and then I deploy it on Firebase and it’s hosted happily ever after.
Now this article has been a bit of fun writing in this style and talking about a project I’ve genuinely enjoyed every minute of, and every up and down. But, what is it actually like using Rust and Parcel? I can wholeheartedly say it is a true pleasure...once you find the right resources. Parcel just makes it so easy with no configuration needed for most projects, and although it might not be as fast on larger projects it will give the big dogs a run for their money 9/10 times!
As for Rust and WASM it was everything I expected and more. Rust has always been a language I have loved programming in and, although it's a challenge, it never gets old. However, if I am to complain about one thing about this experience, it would be the lack of intellisense on the exported JS module. It may not be an issue when you write the tiny Rust file being compiled but I can see this being painful on larger projects using Rust, WASM, and Parcel.
In conclusion, if you have ever had a little voice telling you to give Rust or WASM a go, I would highly recommend it and maybe consider using Parcel to avoid the emotional rollercoaster I went on to get it done!