Recently there has been quite a bit of talk about WebAssembly, a new format for code for the web. It is a compile target for languages like C and Rust that enables us to write, and run, code from these languages in our browser.
In the interest of learning more about this technology (and to avoid writing more Javascript) let's explore together and get our hands dirty!
Disclaimer: WebAssembly is stabilized, but most implementations are not. The information contained here may become out of date or be incorrect, despite working at the time of writing.
Before we start please make sure you're using the current (or developer) version of Firefox or Chrome. You can check out about:config
or chrome://flags/
and make sure wasm
related things are enabled.
What Are We Looking At?
WebAssembly (or wasm) describes an execution environment which browsers can implement within their Javascript Virtual Machines. It's a way to run code in place of, or alongside, Javascript.
WebAssembly can be thought of as similar to asm.js. Indeed, we can use Emscripten compiler to target both.
Most existing documentation discusses how to build C, C++, or Rust into wasm, but there is nothing excluding languages like Ruby or Python from working as well.
Installing The Tools
We'll need two things to get started with WebAssembly and Rust, assuming you already have a functional development environment otherwise (That is, you have build-essential
, XCode, or the like installed.)
First, Rust. You can review here for a more long winded explanation of how to do this, or you can just run the following and accept the defaults:
|
Open a new terminal or run source $HOME/.cargo/env
, then we'll add the wasm32-unknown-emscripten
compile target via rustup
as well:
We can use this command to install other targets, found via rustup target list
, as well.
Next we need to set up Emscripten via emsdk
. We'll use the incoming version of Emscripten in order to get the best output. Note: This may take awhile (an hour!).
|
At this point Emscripten is installed. The last command will instruct us how to add the binaries to our path for permanent usage, or we can just source ./emsdk_env.sh
for temporary fun.
At this point emcc -v
should report something similar to this:
)
)
() )
)
)
Now, let's kick the tires a bit!
First Experiment: Standalone Executable
In our first experiment we'll compile some Rust code into wasm and have it run in the browser console. We'll try a basic code sample to ensure things are working as we expect. We won't try to do any crate importing, DOM manipulation, or network access yet, but that's coming later!
Let's create our project, we'll use this same project for our future experiments as well. We need to set this to nightly (at least at the time or writing) to have our demos work.
Then we'll put our first code sample into src/main.rs
:
Running this normally yields what we would expect:
)
)
Now, let's get the same thing in our browser! We can build our executable for the wasm target by using the --target
flag.
cargo
created several files in target/wasm32-unknown-emscripten/release/deps/
for us. Of primary interest are the .wasm
and .js
files.
Why do we get both a wasm
and a js
? Wasn't the whole point of this to not use Javascript? Turns out we need some Javascript glue code to fetch, initialize, and configure it.
At this point we can't really use the created files, since we don't have a webpage to import them into! Let's work on that. We can create a site/index.html
with the following content:
Next we need to set up some way to get the generated files from the target/
folder into the site/
folder. make
is a good solution for this, so let's make a Makefile
. (Makefiles require tabs, not spaces!)
SHELL :=
:
Finally, test it with make
.
Let's test our generated code by running python -m SimpleHTTPServer
, browsing to http://localhost:8000/site/
, and opening the browser console.
In the browser console you should see:
trying binaryen method: native-wasm
asynchronously preparing wasm
binaryen method succeeded.
The character encoding of the HTML document was not declared. The document will render with garbled text in some browser configurations if the document contains characters from outside the US-ASCII range. The character encoding of the page must be declared in the document or in the transfer protocol.
South
false
Excellent! It worked!
Second Experiment: Imports
In our first example we just used a basic enum and some print statements, nothing too exciting. Let's do something more exciting and work with iterators.
use HashMap;
use *;
Building this code again with make
we can navigate to the page again in our browser. This should output:
Excellent! So importing things from std
seems to work okay. How about other crates? Let's try using itertools
. Add the following to the Cargo.toml
:
[]
= "*"
Then we can go and try to use it.
extern crate itertools;
use Itertools;
use *;
Building and visiting the page again we see the following output:
[North, South, East, West]
Okay that was simple! What about something a bit more complicated, like serde
, which lets us serialize and deserialize various formats? This will be important for an application which needs to parse responses from APIs!
Changing the Cargo.toml
and the main.rs
:
[]
= "*"
= "*"
= "*"
extern crate serde_json;
extern crate serde_derive;
Building, and viewing it in the browser console yields:
Example
Awesome!
Third Experiment: Calling From Javascript
It's also possible to use generated wasm as a library and call the generated code from within Javascript. This has its own set of complications though.
WebAssembly only supports a limited number of value types:
i32
: 32-bit integeri64
: 64-bit integerf32
: 32-bit floating pointf64
: 64-bit floating point
Notice how this doesn't include strings or other more satisfying types. That's ok! We can still use a function called Module.cwrap
to define the parameters and expected return values of the wasm exported functions.
Writing this interaction code means that on the Rust side of things we have to treat it as though we're interacting with C. This can make some things a bit complicated, on the bright side it means writing more FFI (foreign function interfaces) later will be much easier.
So, let's write a basic function which returns a String from Rust into Javascript.
use c_char;
use CString;
use HashMap;
First thing you might notice is that we have a main()
function which is blank. This is on purpose, we're going to define that in Javascript! We'll edit the index.html
to write a bit of new code for this purpose.
The final argument of cwrap
is an array of argument types, which we don't use in this case.
It's also possible to call the function via Module._get_data()
but you'll notice that it returns a pointer to a memory location, not a string.
Fourth Experiment: Calling Javascript From Rust
There isn't a tremendous amount of writing about this topic, the best resource I was able to find was in the 'Interacting with code' section of the Emscripten documentation.
The basic concept is that when emcc
goes and does its job it includes a library.js
file which we can add functions to via the --js-library
flag. These functions can return values, or do things like run alert("Blah blah")
.
In order to do this we need to create a site/utilities.js
file containing the following:
'use strict';
;
LibraryManager.library, library;
Then the HTML:
Lastly, the Rust code:
extern
use c_char;
use CStr;
extern
If we build this and load the page we'll get an alert, and see "Hello from JS"
printed out on the console.
The good thing is it works. The bad thing is that it is rather complex to do. Hopefully it will get better in the future.
I'm not entirely happy with this situation, if you have any ideas how we can do this better please let me know!
Fifth Experiment: Using The Web Platform
As we've discovered by now, the barrier between our compiled code and Javascript is a bit annoying. It'd be quite handy if we could just write everything in Rust and avoid dealing with Javascript except where absolutely needed.
Luckly for us, there is a crate called rust-webplatform
that was started. It allows for convienent DOM manipulation and provides some helpers for doing Javascript. It's still a bit rough around the edges, but the foundations are there.
To get started, let's add webplatform = "*"
to our dependencies. Then we can use some slightly modified sample code from the repo:
extern crate webplatform;
Building our project you should see:
- DOM elements created when the wasm is loaded.
- DOM elements being created when your mouse goes over the button.
- An alert when you click on the button.
Due to the youth of the library there isn't much in the way of examples or documentation, so be prepared to fumble around in the dark. For example, I discovered the key to getting some events and println!()
working was to use webplatform::spin()
.
If you're looking for something to contribute, this crate would likely be an excellent place to do so! There's a bunch of work to be done and you could really make a difference for the blossoming Rust wasm community!
Outlook
The future of Rust on the web is quite bright! Much of the foundations already exist, as we've seen in this article, and the Rust community is actively interested in improving the experience. The Rust repository even has an A-wasm
tag for issues!
There are currently discussions about moving from emcc
to the nascent LLVM wasm backend which you can track here. This may make significant parts of this article obselete, which is very exciting!
The ecosystem around wasm in Rust is still young, and now is a great time to get involved and help shape the future!
Special thanks to Jan-Erik (Badboy) for his workshop at Rust Belt Rust 2016, his hard work on this ecosystem, his advice while writing this post, for reviewing this before publishing, and most of all for being my valued friend. May we continue to hack in the same circles.
This post was supported by Asquera and is an extension of the wasm chapter in our Three Days of Rust course. If you'd like to hire us for training, consulting, or developing in Rust (or anything else) please get in touch with me at andrew.hobden@asquera.de.