Cargo-Culting the Rusty Web: Serializing to JavaScript
A journey into the great unknown. In this post, I’ll share my experiences using the Rust language to target Web Assembly without help from the emscripten toolchain, giving special focus to the pains involved in learning to pass complex data types.

If you want to cut your teeth on Web Assembly but aren’t sure which source language to use, Rust has a pretty strong advantage. Most setups require the emscripten toolchain to be installed in your path, but as of recently, Rust can actually target wasm natively without it. That’s a huge win.
I immediately tried the minimal example and it worked like a charm, so things were looking good. But this code dealt only with integers, and it turned out that more complex data types were a completely different story. In particular, serializing structured data involves some steps that aren’t well documented, and others that are inherently complicated. What follows is the story of how I achieved it: a familiar tale of copying and pasting, of trial and lots and lots of error.
Step 1: Integrate a Serializer
As far as I was able to tell, JSON serialization is not built into the language. If you’re familiar with Rust, then you also know of its dependency management system called cargo. I wanted to use a crate called serde_json, but the documentation on Hello, Rust doesn’t make it quite clear how to integrate cargo into the wasm build process. The path of least resistance most likely starts by using cargo to create your Rust project:
$ cargo new serialize-this --bin
Created binary (application) `serialize-this` project$ cd serialize-this
The folder structure will look like this:
▾ src/
main.rs
Cargo.toml
The file at src/main.rs
is the entry point of our program. Let’s start by adding our dependencies to Cargo.toml
:
[dependencies]
serde = "1.0.24"
serde_json = "1.0.8"
serde_derive = "1.0.24"
Now if we want to use any of these crates in our code, we need to call them out, so let’s do this with the extern
command in our main.rs
:
At this point, we might be tempted to see if it will compile. If we were following the guide, we’d use the rustc
command:
$ rustc +nightly --target wasm32-unknown-unknown -O src/main.rs
But doing this will cause an error.
error[E0463]: can't find crate for `serde_json`
--> src/main.rs:1:1
|
1 | extern crate serde_json;
| ^^^^^^^^^^^^^^^^^^^^^^^^ can't find crateerror: aborting due to previous error
Instead we need to build with cargo so that it will download our crates.
$ cargo +nightly build --release --target=wasm32-unknown-unknown
This will compile a .wasm
file in the release/
folder of our compile target:
$ ls target/wasm32-unknown-unknown/release/serialize-this.wasm
target/wasm32-unknown-unknown/release/serialize-this.wasm
Step 2: Serialize Data
The next trick is to use our crate to return some serialized data from a function. For the backing data structure, we’ll use a simple struct
and annotate it with serde_derive
so that it can be serialized:
#[derive(Serialize)]
struct Sam {
instrument: String
}
Now we can easily create a Sam
object and serialize it like this:
let sam = Sam {
instrument: "saxophone".to_string()
}; let serial = serde_json::to_string(&sam);
The serial
object is one of Rust’s Result types, because there’s a chance the serialization could fail. This means that in order to get the result we want out of it, we have to use a match
statement:
let serial = match serial {
Ok(s) => s.to_string(),
Err(_) => "nope".to_string()
};
Now in order for it to be read from JavaScript, the examples indicate that we should be returning a CString
pointer and following it from the JS. We’re more or less cargo-culting their approach, so, altogether now:
We now have a function that will return serialized data about, well, me. Now, I barely know the first thing about Rust, but if I believe what the demos are telling me, I’ll need a function to deallocate the memory for the string that this function returns:
#[no_mangle]
pub extern "C" fn dealloc_str(ptr: *mut c_char) {
unsafe {
let _ = CString::from_raw(ptr);
}
}
This claims to be unsafe
, but again, I don’t know any better. Before we rebuild, let’s setup our front end. I’m going to have a single .html
file:
<!doctype html><html>
<head>
<script src="index.js"></script>
</head>
<body>
</body>
</html>
My index.js
file is going to load the .wasm
module and then print the about_me
data to the console. Using a couple tricks from the guide, including the copyCStr
function:
Now to bring this all together, I’m going to write a bash script called start.sh
that does the following:
- Compile the Rust project to
.wasm
- Run
wasm-gc
on the compiled output and move it next toindex.html
- Start an http server (using
http-server
from npm)
cargo +nightly build --release --target=wasm32-unknown-unknown && \
wasm-gc target/wasm32-unknown-unknown/release/serialize-this.wasm serialize-this.wasm && \
http-server
Run this script:
bash ./start.sh
Then open the page on your browser. Pop open the developer tools, and you should see the serialized data on your console!
Conclusion
Web Assembly is still very new, and a lot of what needs to be done to communicate between .wasm
and .js
is really low level, low abstraction stuff. Finding a good guide just for the basics can be hard enough; with any luck, this post will help others looking to do things like integrating crates into the program or passing more complex data types from Rust functions to JavaScript.