From a lot of Rust and WASM

Evan Brass
4 min readMay 19, 2020

I’ve been working in Rust on Web Assembly projects a lot lately. First was working through the Raytracing in a Weekend series except I’m using Rust and drawing to an HTML Canvas. Right now I’m working on a template for peer to peer websites that wouldn’t require hosting a WebRTC signaling server. My previous version was all JavaScript including a binary serialization and deserialization setup which I’ve replaced with the Postcard crate. In my projects, I’ve gone from manually instantiating the wasm module to using wasm-pack, and then to using wasm-bindgen without wasm-pack. These are a few little things I’ve picked up during these experiences.

WASM-PACK is optional.

WASM-Pack does a few things: 1) It compiles your code using wasm32-unknown-unknown, 2) It runs the wasm-bindgen cli (matching whatever version of wasm-bindgen you’re using) to generate JS glue 3) Lastly, it runs wasm-opt on the final webassembly

The reason that wasm-pack wasn’t the right fit for me was that I wanted to build multiple tightly coupled libraries and manage the output folder structure including removing the generated package.json. My setup consists of a cargo workspace with three crates: client, shared, and service-worker. Using a cargo workspace means that running cargo build from the top folder builds all three projects into a single target folder and generates a single cargo.lock file. Next step is to run wasm-bindgen-cli on those sources and output them to a folder named wasm. Lastly, I run wasm-opt on the finished wasm-modules. Installing the tools that wasm-pack runs automatically isn’t hard, just use cargo install wasm-bindgen-cli and go get -u github.com/gonowa/wasm-opt to install both tools (assuming you have Rust and Go installed). Compiling with wasm32-unknown-unknown is fairly easy by setting the build target item in your .cargo config file. As you can see, I’m doing most of the same steps as wasm-pack, just slightly differently. I have a script which does these steps here.

WASM-Bindgen is pretty powerful.

WASM-Bindgen can handle pretty sophisticated interfaces. I mostly end up just passing Uint8Arrrays into and out of the wasm module because my projects consist mostly of crypto keys, strings, and images. Frankly, if I could return multiple values than I wouldn’t need a single interface type but since I can’t return tuples to / from JS, I did end up using a simple type to represent a js keypair with public and private key fields. Using js-sys I can dynamically cast to that type from a generic JsValue. The cast performs an instanceof check so if you’re not using classes than make sure your object works with instanceof. Because wasm-bindgen is pretty efficient with the whole in / out of wasm memory stuff I’ve fully switched to letting it manage the interfaces and no longer use pointers manually to pull in and our of the wasm instance’s memory.

Before I learned about js_name I thought I wouldn’t be able to use wasm-bindgen because I needed to import some js to wasm, but js_name I can do just that.

For target web, changing js that is imported by module requires a recompile

Not a big deal, but it’s bitten me a few times. wasm-bindgen copies the code from the es6 modules that you import into the target directory when using the web target (I don’t use bundlers during development so if you do then you might not encounter this problem). This makes the paths easier but does mean that if you change the modules that you import from you’ll need to re-run wasm-bindgen.

no-modules is required for service-workers

I can’t wait to stop using importScripts but for now, service workers aren’t able to use es6 modules. This means running wasm-bindgen with the no-modules target. Since wasm-bindgen doesn’t do any js copying for no-modules, you don’t need to rerun wasm-bindgen when you change JavaScript that is imported into your Rust code. On the down side (as is always the case with importScripts) everything is in the global namespace and dependency management is up to you.

Anyway, that’s some of the things that have affected my development time on my projects. I suspect that as time goes on there will be better and better abstractions over the web apis for Rust and WASM that will reduce the burden of error handling when interacting with them. In my case I’ve just been jumping out to write a bit of javascript that ~safely~ wraps the behavior that I want. One case is for persistent storage. I have a bit of JS that stores Uint8arrays to indexeddb so that anything serializable can be persisted. Using web-sys to wrap that functionality would have been an uncomfortable amount of Result objects when I can just write a smidge of js that conceals all that unsafety.

It’s not ready or even working, but if you want to see a glob of code that spans a client and service worker feel free to look at my web3.0 project.

--

--

Evan Brass

I write a lot of ECMAScript… enough to have plenty of mistakes to learn from.