In Search Of … WebAssembly
When I was a kid, one of my favorite shows was “In Search Of…” If you’re too young to remember, then this was a late 70s/early 80s TV show dedicated to uncovering the truth behind mysterious phenomenon, hosted and narrated by Rod Serling and then Leonard Nimoy. Each episode was dedicated to finding the truth behind some mystery like Bigfoot, mummy curses, physics, and even buried cities like Troy. To me, it exemplified a sense of curiosity, a sense of wonder at the new or unexplained. I open with this not because this blog post will reveal what really happened to Al Capone’s vault, but to say that nothing has piqued my curiosity or my insatiable desire to learn quite like WebAssembly has. I need a TV show that takes me through the ins and outs of wasm (the unofficial abbreviation for WebAssembly). Since no one is going to make such a show, this blog post (the first in a potentially long series) will have to suffice.
When I first heard about WebAssembly, what I wanted my “In Search Of…” episode to cover was:
- What is WebAssembly?
- Why do I care?
- What does it look like?
There are a ton of different aspects of WebAssembly and one thing I encountered when first learning about it was an overwhelming amount of material. Some of it was outdated, some of it was a treatise on what it’ll look like in the future, and much of it built upon concepts I hadn’t yet learned. In this post, I’ll cover the fundamentals so that hopefully future posts can build on that foundation to show some of the really amazing parts of wasm.
What is WebAssembly?
The webassembly.org website says that it is, “a binary instruction format for a stack-based virtual machine”. This definition leaves me…uninformed. It’s technically accurate, but it doesn’t really do much for me. The presence of the word “assembly” in the name tells me that we’re dealing with a low-level language. Wasm is fast, efficient, safe, debuggable, open, and “web native”.
It might help first re-iterating what WebAssembly is NOT:
- JavaScript
- An alternative JavaScript flavor or standard
- A JavaScript “transpile” source or destination
- An “eventually becomes JavaScript” DSL like React’s JSX templates
When you build applications in languages like C and Rust you can target different platform architectures. You can target arm
, mips
, x86
, x86_64
, and now you can target wasm
. I think viewing WebAssembly this way helps put it in the right perspective.
“Regular” assembly is a set of primitive instructions that are compiled into executable machine code that runs on physical hardware. Wasm is a set of primitive instructions that are compiled into executable code that runs on a platform-agnostic, stack-based, virtual machine. As a “web native” entity, most of the time we talk about wasm we’re implying that this VM is hosted by a browser. However, the VM can be hosted by any number of non-browser containers for numerous non-web use cases, including some really interesting ones like server-side or Internet of Things (IoT) devices.
To use WebAssembly, you fire up a wasm virtual machine with a set of memory to be used by the wasm instructions. This is just like a physical machine would allow regular assembly access to RAM. We get a ton of flexibility and power here because as the instantiators of the virtual machine, we supply the memory it uses. This means the browser can supply just enough memory for the wasm
code to run, or it can run in small footprints like on IoT devices.
One of the stated goals of WebAssembly is that it be compiled to “execute at native speed”. This means that even though the wasm code might’ve been loaded by a browser, the code will execute nearly as fast as if it were a native binary already residing on your machine. In fact, this code can even access a common set of hardware functionality, performing tasks we simply cannot in JavaScript.
I’ll show a little of what wasm looks like later, but for now let’s just keep in mind that we use languages like C and Rust to compile into wasm
target binaries. Access to these languages (esp. Rust) leads directly into the why do I care? part of this article.
Why do I care?
Now that we’ve got a basic understanding (I promise I will go into excruciating depth in later posts) of what WebAssembly is, I need to answer what is quite possibly the most important question: why do I care? As developers in 2018, we are under constant assault by a deluge of new languages, new platforms, new frameworks, technologies, and other shiny objects. There is an all-out war for our attention and we must be sparing with it, or we will tear ourselves apart trying to keep up with everything. As such, we need to justify the time investment we’re going to sink into any new shiny — Is this even worth learning about? Will I learn enough about it in a short enough time to make a value decision about whether this is a rabbit hole I should go down?
I can’t make these decisions for you, but I can give you some information that might help make your choices more informed and easier to make. I think the primary reason why I care about wasm is that I believe that the current JavaScript landscape is more of a bomb-blasted hellscape than simple, clean, well-defined fields. Nowhere is the embarrassment of choice more evident than in the JavaScript landscape. There is a nearly infinite number of ways you can build a website with JS, including transpiling one form of JavaScript into another before you even publish your actual site. We are afraid of the raw core of JavaScript, so we wrap it in layer after layer of tooling, artifices of type checking, secondary compilers, transpilers, high-coverage testing, and even a few band-aids.
Don’t get me wrong, many development teams are super productive with JS, and amazing things are possible with it. However, these teams likely spent a long time finding the best practices to reach that level of maturity and are operating on a mountain of tribal knowledge for how to keep the process clean.
JavaScript, for all its dominance on the web today, is slow. Many of us would give anything to be able to write code in a strict, safe, powerful language and deliver fast, compact codebases directly to our users without having to become experts in JavaScript.
There are a ton of use cases for why you might want to use WebAssembly, including the following which are taken from the wasm website:
- Image and video editing
- Games (casual and AAA)
- Peer-to-peer applications
- Music apps
- Interactive and remote tooling
- Fat client for enterprise apps
- Server-side untrusted compute
- Game and app distribution (secure/portable)
My elevator pitch for WebAssembly is usually something like: All of the benefits of the web, without the performance or portability penalties, and the ability to use a robust, safe language like Rust.
What does WebAssembly Look Like?
If you really want the gory details, then you can go and look up the WebAssembly spec. As we progress through more and more complex samples, I’ll introduce different concepts from the WebAssembly spec. While wasm
is a binary standard, there is a text version that can be read by humans the same way there is a text version of regular assembly language for physical processors.
For now, let’s just get our feet wet with a quick hello world and see what all the fuss is about. Another reason for this slow introduction to wasm is that there are many different ways you can compile C or Rust into wasm, each with their own sets of benefits and drawbacks. As tooling and the ecosystem matures these differences will matter less in the future, but for now, I want to keep things as simple as possible because it can get overwhelmingly complex quickly.
For this sample, make sure you’ve got the latest version of Rust installed and create a new library using nightly (the native wasm target is only available in nightly right now):
$ cargo +nightly new --lib hello-world
If you’re not sure how to get Rust installed, just follow the simple directions on Rust’s website. Now let’s write some Rust code. Borrowing right from the tutorials, modify your lib.rs
file to look like this:
#[no_mangle]
pub extern fn add_one(a: u32) -> u32 {
a + 1
}
Here extern
means we’re going to export the function outside of Rust. no_mangle
prevents the function signature from getting rearranged during the compilation process. Next, update the Cargo.toml
file to include a crate-type:
[lib]
crate-type = ["cdylib"]
To build this library and target a wasm
binary, we can compile it as follows:
$ cargo +nightly build --target wasm32-unknown-unknown --release
We should now have a hello_world.wasm
file sitting in our target/wasm32-unknown-unknown/release
directory. On my machine, this was about 600k, but wasm
is very compact and this won’t grow much as you add code.
That’s it! We’ve created a Rust function that adds 1 to a number and returns the result. This was then compiled into a WebAssembly module that can be hosted in a browser or anywhere else that knows how to host wasm modules.
To see how to invoke this function via JavaScript in a browser, check out this tutorial.
I know you were hoping for more than just adding to a number, but let’s recap what we have:
- Code written in Rust — which means we have lifetime checks, borrow checks, inability to compile dangling pointers or data races, plus all the goodies like pattern matching, robust enums, traits, etc.
- Statically typed code, checked at compile time, without the need for webpack, npm, etc (though as you’ll see, we can integrate those if we choose)
- A compact, portable, binary module format that can be launched in a browser or any other host.
What this really represents is the foundation for the new way of building applications for the web (and other uses). I really believe that WebAssembly is going to take off in a way that so many of its predecessors (Flash/ActionScript, Silverlight, and their ilk) have failed to do. Stay tuned for more posts in this series where I’ll walk you through progressively more powerful and amazing things you can build with WebAssembly and Rust!