OK, I LOVE WebAssembly (WASM). I really do, as a concept, as a medium, as an architecture, It’s so cool. I’ve spent a veritable week of my life in the innards of its tooling and, while I still love it, it’s the kind of love that you give a disabled pet. You love it, and you hate yourself a little for pitying it, and wonder whether you love it because you’re a good person, or because of the pity.
Yeah, it’s SAD.
What’s WASM?
For those not in the know, but are still reading this for some reason: MDN puts its definition as:
“WebAssembly is a low-level assembly-like language with a compact binary format that runs with near-native performance and provides languages such as C/C++, C#, [Go], and Rust with a compilation target so that they can run on the web. It is also designed to run alongside JavaScript, allowing both to work together.”
In terms of this specific application of the technology, it works very well. You can build stuff, compile it, and then chuck it in a browser and it will run. Very cool. Very nice.
However, that’s not where the real brilliance of WASM lives. You see, WebAssembly comes with an implicit promise of sandboxing and security. From the ground up, it was designed to be sandboxed, so that whatever ran inside the runtime could not ever escape or do anything naughty on the machine running it.
Yes, more secure than Virtual Machines, and more secure than Docker Containers. It manages this by having absolutely zero access outside of its runtime environment, not even shared memory. Instead, the host must feed a WASM module any and all capability it might need to interact with the outside world.
This extreme sandboxing gives WASM the ability to be the most secure server runtime ever, specifically for microservices and Functions-as-a-Service. Because of its near-native performance, it runs fast anywhere, has very low spin-up time (unlike a docker container), and can be scaled to zero, in fact a WASM module host server doesn’t even need to be a full OS, it can be specialized unikernel to make it even more secure.
Amazing, right?
Unfortunately, it isn’t meant to be… yet. More unfortunately, it hasn’t been meant to be for the past 5 years.
The promise of WASM as a server-side solution hinges on the adoption by compilers of the Web Assembly System Interface (WASI for short). WASI is meant to provide standard interfaces for system I/O such as file system, networking, and other kernel-level interactions usually provided by syscalls under the hood. Making WASI a reality, and fully-fledged, would make it possible to easily spin up web servers, TCP Clients, and file-servers using out-of-the-box libraries that come with your preferred language.
Now, I need to caveat this – my experience with WASM comes from Go. And I will admit that the tooling and capability in the ecosystem is better supported in languages like Rust. However, I’m not insane and I also don’t hate myself quite enough to go and pick up Rust – so don’t @-me.
So, what’s the problem?
Well, let’s start with I/O. The most basic interactions between a host and a module will be via memory, and this is where it starts getting complicated very very quickly. You’d think you can define a function in your client module and then call it from the host, or vice-versa. And that is true – to an extent.
What they don’t tell you right off the bat (and all of the examples for this wonderful interoperability fail to really explain) is that you can only pass numeric types between host and client.
Why? Because host and client can only pass objects to one another through a shared memory buffer which you then need to manually allocate, address, read, write, and clear as you use the module. That’s right, `malloc` is back.
Ok, so you may think that this is manageable, you could probably abstract it away into a library and then make it easier to use for normal humans, unfortunately, you’re got again – not every compiler will give you hooks into the shared memory, or if they do, it’s undocumented, and even if it’s documented, it’s different between compilers!
So, for example, TinyGo will give you a `malloc()` function, but Go’s standard compiler won’t, you need to provide that yourself. If you use Rust, you get `mc()`. This is bad – because in order to clearly work between host and client, the host needs to be able to make safe assumptions about how it can communicate with the client. Having different memory function names makes that a difficult task. If you try to standardize it by creating a module-level shared library that provides these exports, then you are tied down to that language, unless you can translate that lib to other languages, and now you have a nightmare dependency tree to try and fulfill the baseline promise of WebAssembly’s “Compile once run anywhere” credo.
WASI is still not fully defined
Now, there’s a second kick in the goolies: WASI (which I mentioned earlier) is not implemented the same, everywhere, to completion. It’s an alpha standard, and its implementations are still being fully defined. So, we’re in a position where it’s compiler-dependent, and runtime-dependent. Both need to support WASI fully in order to properly work.
Guess what? The current runtime ecosystem is fragmented, and the only things you are likely to get support for are file and standard I/O. Networking support is possible, but it is *hard*, and again, it’s not a simple case of “plug and play”, it’s always an edge case.
The reason why containers are so prevalent is because they let you work with the tooling that best supports the task, and you do not need to modify anything in order to get it to work. Your code runs in a container the same way it runs on your machine, and edge-cases come up far less than they do with custom binaries or modules in WebAssembly.
In short, WASM is a pain in the ass to use, and its benefits are just not enough to tip the scales away from easier-to-adopt technology such as containers.
And now you know why I’m pouty about WASM – it has so much promise, it’s so deeply cool. But it’s not there yet, and I have doubts it will ever get there.
I’ll leave you with this tweet from the creator of Docker from 2019. It’s been 5 years since then, and in my humble opinion, WASI is not up to the task, and isn’t about to get there, either.