WebAssembly from Scratch / Part Two
In the Part One of the series, we put together a brand new development environment and created a sample “Hello word” Web Assembly based on the npm template. In this part, we will play with the generated code and go over different ways of loading the Web Assembly module and making the calls into it.
Ties that bind
First, let’s take a good look at the Rust source code that was created by the template. Open file
src/lib.rs and right before the body of the
main_js function there will be a attribute
src/lib.rs looks like this snippet:
According to the wasm-bindgen documentation:
When attached to a
pubfunction this attribute will configure the
startsection of the wasm executable to be emitted, executing the tagged function as soon as the wasm module is instantiated.
In other words, this function will be called automatically as soon as the Web Assembly loaded, which makes it a great place for running the initialization code.
Let’s see how exactly this attribute controls the code generation: open the auto-generated
pkg/index.js and scroll to the very bottom – the last line will read
wasm.__wbindgen_start(); which in turn invokes
main_js. Now let’s edit the wasm_bindgen attribute and remove the “start” parameter, like so:
#[wasm_bindgen()]. Rebuild the project. If you look at the
pub/index.js now, you will notice that the call to
__wbindgen_start() is gone and
main_js is now just a regular export from the module.
#[wasm_bindgen(js_name=initModule)]. Now when we rebuild the project, the exported function is called
initModule instead of
There is a number of other attributes that control the way the “glue” code is generated. Play with them and see how they affect the interop
pkg/index.js – it’s fun!
Making the call
Now we have everything we need to start making actual calls to the assembly methods. First, let’s add a very simple method that would take one argument and return a formatted string. Edit
src/lib.rs and add the following snippet:
Next, we edit the corresponding loader
js/index.js to call our new method
What exactly is happening here? First, note that the
- Load the WASM module (either by using
fetch()or by making AJAX call or using a library). Loading WASM as a
<script>tag is not supported – yet
- Once loaded, you would need to check whether the browser supports
WebAssembly.instantiateStreaming()(see here) and if it does, use it to instantiate the module, otherwise falling back on
WebAssembly.instantiate()as described here
- Only then you would have access to the WASM module instance and would be able to start making calls
Luckily, Webpack takes care of all the heavy lifting. If curious, take a look at the compiled
dist/index.js (you may need to “beautify” it first)
Now run our new code (in Visual Studio Code, “Terminal > Run Task > npm start” or by typing “
npm start” in the command prompt if you prefer to run it manually) and check out the browser console. Mine looks like this:
Instead of a string, we passed in a number. Unsurprisingly, the browser console shows errors:
Actually, this error was thrown by the “interop” code that was created by wasm-pack. We did not have to write a single line of code to check the arguments, it all happened automatically!
Re-run our app and check the console. Voila!
Throwing an exception
Err(...) from the function call. Let’s modify our previous example to “throw” if the parameter has a wrong type.
First, we change our Rust code (
There are quite a few things to unpack here. First, we switched from returning a string to returning a
Result (documented here, with wasm-bindgen specific remarks here). The first type argument here (
String) is the type of the return value that the function is supposed to return and the second type argument (
JsValue) is the type of the “error” that function will raise if something goes wrong. Due to the way wasm-bindgen implements the
Result, we are only allowed to use the
Result<T, JsValue> form (in other words, the “error type” has to be
Run the project and pop up the browser console. Mine looks like the following:
A word of caution
You need to be careful with “throwing exceptions” out of WASM and always manage your object’s lifetime to avoid potential leaks
In the following parts, we will take a look at memory allocation and WASM memory handling in general
Let me know what you think!
Did you have any problem following this guide? Do you know a better or easier way of getting the job done? What will you build next? Let me know!