Introduction

wasm-bindgen facilitates high-level interactions between wasm modules and JavaScript.

This project is sort of half polyfill for features like the host bindings proposal and half features for empowering high-level interactions between JS and wasm-compiled code (currently mostly from Rust). More specifically this project allows JS/wasm to communicate with strings, JS objects, classes, etc, as opposed to purely integers and floats. Using wasm-bindgen for example you can define a JS class in Rust or take a string from JS or return one. The functionality is growing as well!

Currently this tool is Rust-focused but the underlying foundation is language-independent, and it's hoping that over time as this tool stabilizes that it can be used for languages like C/C++!

Notable features of this project includes:

  • Importing JS functionality in to Rust such as DOM manipulation, console logging, or performance monitoring.
  • Exporting Rust functionality to JS such as classes, functions, etc.
  • Working with rich types like strings, numbers, classes, closures, and objects rather than simply u32 and floats.

This project is still relatively new but feedback is of course always welcome! If you're curious about the design plus even more information about what this crate can do, check out the design doc.

A Whirlwind Tour of wasm-bindgen

What follows is a whirlwind tour of wasm-bindgen.

You will learn:

  • Setting up your development environment for wasm-bindgen
  • Importing JavaScript functions and classes into Rust
  • Exporting Rust structs and functions to JavaScript

Basic Usage

Let's implement the equivalent of "Hello, world!" for this crate.

Note: Currently this projects uses nightly Rust which you can acquire through rustup and configure with rustup default nightly

If you'd like you can dive straight into an online example, but if you'd prefer to follow along in your own console let's install the tools we need:

$ rustup target add wasm32-unknown-unknown --toolchain nightly
$ cargo +nightly install wasm-bindgen-cli

The first command here installs the wasm target so you can compile to it, and the latter will install the wasm-bindgen CLI tool we'll be using later.

Next up let's make our project

$ cargo +nightly new js-hello-world --lib

Now let's add a dependency on this project inside Cargo.toml as well as configuring our build output:

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

Next up our actual code! We'll write this in src/lib.rs:

extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

And that's it! If we were to write the greet function naively without the #[wasm_bindgen] attribute then JS wouldn't be able to communicate with the types like str, so slapping a #[wasm_bindgen] on the function and the import of alert ensures that the right shims are generated.

Next up let's build our project:

$ cargo +nightly build --target wasm32-unknown-unknown

After this you'll have a wasm file at target/wasm32-unknown-unknown/debug/js_hello_world.wasm. Don't be alarmed at the size, this is an unoptimized program!

Now that we've generated the wasm module it's time to run the bindgen tool itself! This tool will postprocess the wasm file rustc generated, generating a new wasm file and a set of JS bindings as well. Let's invoke it!

$ wasm-bindgen target/wasm32-unknown-unknown/debug/js_hello_world.wasm \
  --out-dir .

This is the main point where the magic happens. The js_hello_world.wasm file emitted by rustc contains descriptors of how to communicate via richer types than wasm currently supports. The wasm-bindgen tool will interpret this information, emitting a replacement module for the wasm file.

The previous js_hello_world.wasm file is interpreted as if it were an ES6 module. The js_hello_world.js file emitted by wasm-bindgen should have the intended interface of the wasm file, notably with rich types like strings, classes, etc.

The wasm-bindgen tool also emits a few other files needed to implement this module. For example js_hello_world_bg.wasm is the original wasm file but postprocessed a bit. It's intended that the js_hello_world_bg.wasm file, like before, acts like an ES6 module.

At this point you'll probably plug these files into a larger build system. Files emitted by wasm-bindgen act like normal ES6 modules (one just happens to be wasm). As of the time of this writing there's unfortunately not a lot of tools that natively do this, but Webpack's 4.0 beta release has native wasm support! Let's take a look at that and see how it works.

First, create an index.js file:

const js = import("./js_hello_world");

js.then(js => {
  js.greet("World!");
});

Note that we're using import(..) here because Webpack doesn't support synchronously importing modules from the main chunk just yet.

Next, define our JS dependencies by creating a package.json:

{
  "scripts": {
    "serve": "webpack-dev-server"
  },
  "devDependencies": {
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.0.1",
    "webpack-cli": "^3.1.1",
    "webpack-dev-server": "^3.1.0"
  }
}

and our webpack configuration:

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: "./index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "index.js",
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Getting started with WASM"
    })
  ],
  mode: "development"
};

And finally:

$ npm install
$ npm run serve

If you open http://localhost:8080 in a browser you should see a Hello, world! dialog pop up!

Notice that html-webpack-plugin has generated an HTML page which includes index.js.

Finally, you may want to deploy your application to a web server like Apache or NGINX. For that, simply run:

$ npx webpack

The output will be in the dist directory. You can now copy it to the root of your web server.

If that was all a bit much, no worries! You can execute this code online thanks to WebAssembly Studio or you can follow along on GitHub to see all the files necessary as well as a script to set it all up.

What Just Happened?

Phew! That was a lot of words and a lot ended up happening along the way. There were two main pieces of magic happening: the #[wasm_bindgen] attribute and the wasm-bindgen CLI tool.

The #[wasm_bindgen] attribute

This attribute, exported from the wasm-bindgen crate, is the entrypoint to exposing Rust functions to JS. This is a procedural macro (hence requiring the nightly Rust toolchain) which will generate the appropriate shims in Rust to translate from your type signature to one that JS can interface with. Finally the attribute also serializes some information to the output artifact which wasm-bindgen-the-tool will discard after it parses.

There's a more thorough explanation below of the various bits and pieces of the attribute, but it suffices for now to say that you can attach it to free functions, structs, impl blocks for those structs and extern "C" { ... } blocks. Some Rust features like generics, lifetime parameters, etc, aren't supported on functions tagged with #[wasm_bindgen] right now.

The wasm-bindgen CLI tool

The next half of what happened here was all in the wasm-bindgen tool. This tool opened up the wasm module that rustc generated and found an encoded description of what was passed to the #[wasm_bindgen] attribute. You can think of this as the #[wasm_bindgen] attribute created a special section of the output module which wasm-bindgen strips and processes.

This information gave wasm-bindgen all it needed to know to generate the JS file that we then imported. The JS file wraps instantiating the underlying wasm module (aka calling WebAssembly.instantiate) and then provides wrappers for classes/functions within.

What Else Can We Do?

Much more! Here's a taste of various features you can use in this project. You can also explore this code online:

// src/lib.rs
extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

// Strings can both be passed in and received
#[wasm_bindgen]
pub fn concat(a: &str, b: &str) -> String {
    let mut a = a.to_string();
    a.push_str(b);
    return a
}

// A struct will show up as a class on the JS side of things
#[wasm_bindgen]
pub struct Foo {
    contents: u32,
}

#[wasm_bindgen]
impl Foo {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Foo {
        Foo { contents: 0 }
    }

    // Methods can be defined with `&mut self` or `&self`, and arguments you
    // can pass to a normal free function also all work in methods.
    pub fn add(&mut self, amt: u32) -> u32 {
        self.contents += amt;
        return self.contents
    }

    // You can also take a limited set of references to other types as well.
    pub fn add_other(&mut self, bar: &Bar) {
        self.contents += bar.contents;
    }

    // Ownership can work too!
    pub fn consume_other(&mut self, bar: Bar) {
        self.contents += bar.contents;
    }
}

#[wasm_bindgen]
pub struct Bar {
    contents: u32,
    opaque: JsValue, // defined in `wasm_bindgen`, imported via prelude
}

#[wasm_bindgen(module = "./index")] // what ES6 module to import from
extern "C" {
    fn bar_on_reset(to: &str, opaque: &JsValue);

    // We can import classes and annotate functionality on those classes as well
    type Awesome;
    #[wasm_bindgen(constructor)]
    fn new() -> Awesome;
    #[wasm_bindgen(method)]
    fn get_internal(this: &Awesome) -> u32;
    // We can call javascript functions that have a dynamic number of arguments,
    // e.g. rust `sum(&[1, 2, 3])` will be called like `sum(1, 2, 3)`
    #[wasm_bindgen(variadic)]
    fn sum(vals: &[u32]) -> u32;
}

#[wasm_bindgen]
impl Bar {
    pub fn from_str(s: &str, opaque: JsValue) -> Bar {
        let contents = s.parse().unwrap_or_else(|_| {
            Awesome::new().get_internal()
        });
        Bar { contents, opaque }
    }

    pub fn reset(&mut self, s: &str) {
        if let Ok(n) = s.parse() {
            bar_on_reset(s, &self.opaque);
            self.contents = n;
        }
    }
}

The generated JS bindings for this invocation of the macro look like this. You can view them in action like so:

and our corresponding index.js:

import { Foo, Bar, concat } from "./js_hello_world";
import { booted } from "./js_hello_world_wasm";

export function bar_on_reset(s, token) {
  console.log(token);
  console.log(`this instance of bar was reset to ${s}`);
}

function assertEq(a, b) {
  if (a !== b)
    throw new Error(`${a} != ${b}`);
  console.log(`found ${a} === ${b}`);
}

function main() {
  assertEq(concat('a', 'b'), 'ab');

  // Note that to use `new Foo()` the constructor function must be annotated
  // with `#[wasm_bindgen(constructor)]`, otherwise only `Foo.new()` can be used.
  // Additionally objects allocated corresponding to Rust structs will need to
  // be deallocated on the Rust side of things with an explicit call to `free`.
  let foo = new Foo();
  assertEq(foo.add(10), 10);
  foo.free();

  // Pass objects to one another
  let foo1 = new Foo();
  let bar = Bar.from_str("22", { opaque: 'object' });
  foo1.add_other(bar);

  // We also don't have to `free` the `bar` variable as this function is
  // transferring ownership to `foo1`
  bar.reset('34');
  foo1.consume_other(bar);

  assertEq(foo1.add(2), 22 + 34 + 2);
  foo1.free();

  alert('all passed!')
}

export class Awesome {
  constructor() {
    this.internal = 32;
  }

  get_internal() {
    return this.internal;
  }
}

export function sum(...args) {
    let answer = 0;
    for(var i=0; i<args.length; i++) {
        answer += args[i];
    }
    return answer;
}

booted.then(main);

Examples of using wasm-bindgen, js-sys, and web-sys

This subsection contains examples of using the wasm-bindgen, js-sys, and web-sys crates. Each example should have more information about what it's doing.

The source code for all examples can also be found online to download an run locally. Each example is accompanied with a build.sh script to outline the steps necessary to build and run it as well.

Note that most examples currently use Webpack to assemble the final output artifact, but this is not required! You can use the bundler of choice, --no-modules, or native browser ESM support as alternatives to Webpack.

Hello, World!

View full source code or view the compiled example online

This is the "Hello, world!" example of #[wasm_bindgen] showing how to set up a project, export a function to JS, call it from JS, and then call the alert function in Rust.

Cargo.toml

The Cargo.toml enables depends on the wasm-bindgen crate. Here we're using a path dependency because this example lives in the wasm-bindgen repository itself, but you'd use the commented out version beneath it instead.

Also of note is the crate-type = ["cdylib"] which is largely used for wasm final artifacts today.

[package]
name = "hello_world"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2.29"

src/lib.rs

Here we define our Rust entry point along with calling the alert function.


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

#}

index.js

Our JS entry point is quite small!

// Note that a dynamic `import` statement here is required due to
// webpack/webpack#6615, but in theory `import { greet } from './hello_world';`
// will work here one day as well!
const rust = import('./hello_world');

rust
  .then(m => m.greet('World!'))
  .catch(console.error);

Webpack-specific files

And finally here's the Webpack configuration and package.json for this project:

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');

module.exports = {
    entry: './index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js',
    },
    plugins: [
        new HtmlWebpackPlugin(),
        // Have this example work in Edge which doesn't ship `TextEncoder` or
        // `TextDecoder` at this time.
        new webpack.ProvidePlugin({
          TextDecoder: ['text-encoding', 'TextDecoder'],
          TextEncoder: ['text-encoding', 'TextEncoder']
        })
    ],
    mode: 'development'
};

package.json

{
  "scripts": {
    "build": "webpack",
    "serve": "webpack-dev-server"
  },
  "devDependencies": {
    "text-encoding": "^0.7.0",
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.11.1",
    "webpack-cli": "^3.1.1",
    "webpack-dev-server": "^3.1.0"
  }
}

console.log

View full source code or view the compiled example online

This example shows off how to use console.log in a variety of ways, all the way from bare-bones usage to a println!-like macro with web_sys.

src/lib.rs


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen(start)]
pub fn run() {
    bare_bones();
    using_a_macro();
    using_web_sys();
}

// First up let's take a look of binding `console.log` manually, without the
// help of `web_sys`. Here we're writing the `#[wasm_bindgen]` annotations
// manually ourselves, and the correctness of our program relies on the
// correctness of these annotations!

#[wasm_bindgen]
extern "C" {
    // Use `js_namespace` here to bind `console.log(..)` instead of just
    // `log(..)`
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);

    // The `console.log` is quite polymorphic, so we can bind it with multiple
    // signatures. Note that we need to use `js_name` to ensure we always call
    // `log` in JS.
    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn log_u32(a: u32);

    // Multiple arguments too!
    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn log_many(a: &str, b: &str);
}

fn bare_bones() {
    log("Hello from Rust!");
    log_u32(42);
    log_many("Logging", "many values!");
}

// Next let's define a macro that's like `println!`, only it works for
// `console.log`. Note that `println!` doesn't actually work on the wasm target
// because the standard library currently just eats all output. To get
// `println!`-like behavior in your app you'll likely want a macro like this.

macro_rules! console_log {
    // Note that this is using the `log` function imported above during
    // `bare_bones`
    ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}

fn using_a_macro() {
    console_log!("Hello {}!", "world");
    console_log!("Let's print some numbers...");
    console_log!("1 + 3 = {}", 1 + 3);
}

// And finally, we don't even have to define the `log` function ourselves! The
// `web_sys` crate already has it defined for us.

fn using_web_sys() {
    use web_sys::console;

    console::log_1(&"Hello using web-sys".into());

    let js: JsValue = 4.into();
    console::log_2(&"Logging arbitrary values looks like".into(), &js);
}

#}

Small wasm files

View full source code or view the compiled example online

One of wasm-bindgen's core goals is a pay-only-for-what-you-use philosophy, so if we don't use much then we shouldn't be paying much! As a result #[wasm_bindgen] can generate super-small executables

Currently this code...


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: u32, b: u32) -> u32 {
    a + b
}

#}

generates a 710 byte wasm binary:

$ ls -l add_bg.wasm
-rw-rw-r-- 1 alex alex 710 Sep 19 17:32 add_bg.wasm

If you run wasm-opt, a C++ tool for optimize WebAssembly, you can make it even smaller too!

$ wasm-opt -Os add_bg.wasm -o add.wasm
$ ls -l add.wasm
-rw-rw-r-- 1 alex alex 172 Sep 19 17:33 add.wasm

And sure enough, using the wasm2wat tool it's quite small!

$ wasm2wat add.wasm
(module
  (type (;0;) (func (param i32 i32) (result i32)))
  (func (;0;) (type 0) (param i32 i32) (result i32)
    get_local 1
    get_local 0
    i32.add)
  (table (;0;) 1 1 anyfunc)
  (memory (;0;) 17)
  (global (;0;) i32 (i32.const 1049118))
  (global (;1;) i32 (i32.const 1049118))
  (export "memory" (memory 0))
  (export "__indirect_function_table" (table 0))
  (export "__heap_base" (global 0))
  (export "__data_end" (global 1))
  (export "add" (func 0))
  (data (i32.const 1049096) "invalid malloc request"))

Also don't forget to compile in release mode for the smallest binaries! For larger applications you'll likely also want to turn on LTO to generate the smallest binaries:

[profile.release]
lto = true

Using --no-modules

View full source code

This example shows how the --no-modules flag can be used load code in a browser directly (using the same code as the hello world example). Most of the magic happens in index.html:

<html>
  <head>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
  </head>
  <body>
    <script>
      // The `--no-modules`-generated JS from `wasm-bindgen` attempts to use
      // `WebAssembly.instantiateStreaming` to instantiate the wasm module,
      // but this doesn't work with `file://` urls. This example is frequently
      // viewed by simply opening `index.html` in a browser (with a `file://`
      // url), so it would fail if we were to call this function!
      //
      // Work around this for now by deleting the function to ensure that the
      // `no_modules.js` script doesn't have access to it. You won't need this
      // hack when deploying over HTTP.
      delete WebAssembly.instantiateStreaming;
    </script>

    <!-- this is the JS generated by the `wasm-bindgen` CLI tool -->
    <script src='./no_modules.js'></script>

    <script>
      window.addEventListener('load', async () => {
          // the `wasm_bindgen` global is set to the exports of the Rust module
          //
          // here we tell bindgen the path to the wasm file so it can run
          // initialization and return to us a promise when it's done
          // also, we can use 'await' on the returned promise
          await wasm_bindgen('./no_modules_bg.wasm');
      });
    </script>
  </body>
</html>

And that's it! It's worth pointing out that if #[wasm_bindgen(module = "...")] imports are used then wasm-bindgen --no-modules will fail (as it doesn't know how to import modules).

Converting WebAssembly to JS

View full source code

Not all browsers have support for WebAssembly at this time (although all major ones do). If you'd like to support older browsers, you probably want a method that doesn't involve keeping two codebases in sync!

Thankfully there's a tool from binaryen called wasm2js to convert a wasm file to JS. This JS file, if successfully produced, is equivalent to the wasm file (albeit a little bit larger and slower), and can be loaded into practically any browser.

This example is relatively simple (cribbing from the [console.log example][console_log]):


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

// lifted from the `console_log` example
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen]
pub fn run() {
    log("Hello, World!");
}

#}

The real magic happens when you actually build the app. Just after wasm-bindgen we see here how we execute wasm2js in our build script:

#!/bin/sh

set -ex

# Compile our wasm module and run `wasm-bindgen`
cargo build --target wasm32-unknown-unknown --release
cargo run --manifest-path ../../crates/cli/Cargo.toml \
  --bin wasm-bindgen -- \
  ../../target/wasm32-unknown-unknown/release/wasm2js.wasm --out-dir .

# Run the `wasm2js` tool from `binaryen`
wasm2js wasm2js_bg.wasm -o wasm2js_bg.js

# Move our original wasm out of the way to avoid cofusing Webpack.
mv wasm2js_bg.wasm wasm2js_bg.bak.wasm

npm install
npm run serve

Note that the wasm2js tool is still pretty early days so there's likely to be a number of bugs to run into or work around. If any are encountered though please feel free to report them upstream!

Also note that eventually this will ideally be automatically done by your bundler and no action would be needed from you to work in older browsers via wasm2js!

Importing non-browser JS

View full source code or view the compiled example online

The #[wasm_bindgen] attribute can be used on extern "C" { .. } blocks to import functionality from JS. This is how the js-sys and the web-sys crates are built, but you can also use it in your own crate!

For example if you're working with this JS file:

// defined-in-js.js
export function name() {
    return 'World';
}

export class MyClass {
    constructor() {
        this._number = 42;
    }

    get number() {
        return this._number;
    }

    set number(n) {
        return this._number = n;
    }

    render() {
        return `My number is: ${this.number}`;
    }
}

you can use it in Rust with:


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen(module = "./defined-in-js")]
extern "C" {
    fn name() -> String;

    type MyClass;

    #[wasm_bindgen(constructor)]
    fn new() -> MyClass;

    #[wasm_bindgen(method, getter)]
    fn number(this: &MyClass) -> u32;
    #[wasm_bindgen(method, setter)]
    fn set_number(this: &MyClass, number: u32) -> MyClass;
    #[wasm_bindgen(method)]
    fn render(this: &MyClass) -> String;
}

// lifted from the `console_log` example
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen(start)]
pub fn run() {
    log(&format!("Hello, {}!", name()));

    let x = MyClass::new();
    assert_eq!(x.number(), 42);
    x.set_number(10);
    log(&x.render());
}

#}

You can also explore the full list of ways to configure imports

Working with the char type

View full source code or view the compiled example online

The #[wasm_bindgen] macro will convert the rust char type to a single code-point js string, and this example shows how to work with this.

Opening this example should display a single counter with a random character for it's key and 0 for its count. You can click the + button to increase a counter's count. By clicking on the "add counter" button you should see a new counter added to the list with a different random character for it's key.

Under the hood javascript is choosing a random character from an Array of characters and passing that to the rust Counter struct's constructor so the character you are seeing on the page has made the full round trip from js to rust and back to js.

src/lib.rs


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

// lifted from the `console_log` example
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen]
#[derive(Debug)]
pub struct Counter {
    key: char,
    count: i32,
}

#[wasm_bindgen]
impl Counter {
    pub fn default() -> Counter {
        log("Counter::default");
        Self::new('a', 0)
    }
    pub fn new(key: char, count: i32) -> Counter {
        log(&format!("Counter::new({}, {})", key, count));
        Counter {
            key: key,
            count: count,
        }
    }

    pub fn key(&self) -> char {
        log("Counter.key()");
        self.key
    }

    pub fn count(&self) -> i32 {
        log("Counter.count");
        self.count
    }

    pub fn increment(&mut self) {
        log("Counter.increment");
        self.count += 1;
    }

    pub fn update_key(&mut self, key: char) {
        self.key = key;
    }
}

#}

index.js

/* eslint-disable no-unused-vars */
import { chars } from './chars-list.js';
let imp = import('./char.js');
let mod;

let counters = [];
imp
  .then(wasm => {
      mod = wasm;
      addCounter();
      let b = document.getElementById('add-counter');
      if (!b) throw new Error('Unable to find #add-counter');
      b.addEventListener('click', ev => addCounter());
  })
  .catch(console.error);

function addCounter() {
    let ctr = mod.Counter.new(randomChar(), 0);
    counters.push(ctr);
    update();
}

function update() {
    let container = document.getElementById('container');
    if (!container) throw new Error('Unable to find #container in dom');
    while (container.hasChildNodes()) {
        if (container.lastChild.id == 'add-counter') break;
        container.removeChild(container.lastChild);
    }
    for (var i = 0; i < counters.length; i++) {
        let counter = counters[i];
        container.appendChild(newCounter(counter.key(), counter.count(), ev => {
            counter.increment();
            update();
        }));
    }
}

function randomChar() {
    console.log('randomChar');
    let idx = Math.floor(Math.random() * (chars.length - 1));
    console.log('index', idx);
    let ret = chars.splice(idx, 1)[0];
    console.log('char', ret);
    return ret;
}

function newCounter(key, value, cb) {
    let container = document.createElement('div');
    container.setAttribute('class', 'counter');
    let title = document.createElement('h1');
    title.appendChild(document.createTextNode('Counter ' + key));
    container.appendChild(title);
    container.appendChild(newField('Count', value));
    let plus = document.createElement('button');
    plus.setAttribute('type', 'button');
    plus.setAttribute('class', 'plus-button');
    plus.appendChild(document.createTextNode('+'));
    plus.addEventListener('click', cb);
    container.appendChild(plus);
    return container;
}

function newField(key, value) {
    let ret = document.createElement('div');
    ret.setAttribute('class', 'field');
    let name = document.createElement('span');
    name.setAttribute('class', 'name');
    name.appendChild(document.createTextNode(key));
    ret.appendChild(name);
    let val = document.createElement('span');
    val.setAttribute('class', 'value');
    val.appendChild(document.createTextNode(value));
    ret.appendChild(val);
    return ret;
}

js-sys: WebAssembly in WebAssembly

View full source code or view the compiled example online

Using the js-sys crate we can get pretty meta and instantiate WebAssembly modules from inside WebAssembly modules!

src/lib.rs


# #![allow(unused_variables)]
#fn main() {
use js_sys::{Function, Object, Reflect, Uint8Array, WebAssembly};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;

// lifted from the `console_log` example
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(a: &str);
}

macro_rules! console_log {
    ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}

const WASM: &[u8] = include_bytes!("add.wasm");

#[wasm_bindgen(start)]
pub fn run() -> Result<(), JsValue> {
    console_log!("instantiating a new wasm module directly");
    let my_memory = wasm_bindgen::memory()
        .dyn_into::<WebAssembly::Memory>()
        .unwrap();

    // Note that this is somewhat dangerous, once we look at our
    // `WebAssembly.Memory` buffer then if we allocate more pages for ourself
    // (aka do a memory allocation in Rust) it'll cause the buffer to change.
    // That means we can't actually do any memory allocations after we do this
    // until we pass it back to JS.
    let my_memory = Uint8Array::new(&my_memory.buffer()).subarray(
        WASM.as_ptr() as u32,
        WASM.as_ptr() as u32 + WASM.len() as u32,
    );
    let a = WebAssembly::Module::new(my_memory.as_ref())?;
    let b = WebAssembly::Instance::new(&a, &Object::new())?;
    let c = b.exports();

    let add = Reflect::get(c.as_ref(), &"add".into())?
        .dyn_into::<Function>()
        .expect("add export wasn't a function");

    let three = add.call2(&JsValue::undefined(), &1.into(), &2.into())?;
    console_log!("1 + 2 = {:?}", three);
    let mem = Reflect::get(c.as_ref(), &"memory".into())?
        .dyn_into::<WebAssembly::Memory>()
        .expect("memory export wasn't a `WebAssembly.Memory`");
    console_log!("created module has {} pages of memory", mem.grow(0));
    console_log!("giving the module 4 more pages of memory");
    mem.grow(4);
    console_log!("now the module has {} pages of memory", mem.grow(0));

    Ok(())
}

#}

web-sys: DOM hello world

View full source code or view the compiled example online

Using web-sys we're able to interact with all the standard web platform methods, including those of the DOM! Here we take a look at a simple "Hello, world!" which manufactures a DOM element in Rust, customizes it, and then appends it to the page.

Cargo.toml

You can see here how we depend on web-sys and activate associated features to enable all the various APIs:

[package]
name = "dom"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2.29"

[dependencies.web-sys]
version = "0.3.4"
features = [
  'Document',
  'Element',
  'HtmlElement',
  'Node',
  'Window',
]

src/lib.rs


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

// Called by our JS entry point to run the example
#[wasm_bindgen(start)]
pub fn run() -> Result<(), JsValue> {
    // Use `web_sys`'s global `window` function to get a handle on the global
    // window object.
    let window = web_sys::window().expect("no global `window` exists");
    let document = window.document().expect("should have a document on window");
    let body = document.body().expect("document should have a body");

    // Manufacture the element we're gonna append
    let val = document.create_element("p")?;
    val.set_inner_html("Hello from Rust!");

    body.append_child(&val)?;

    Ok(())
}

#}

web-sys: Closures

View full source code or view the compiled example online

One of the features of #[wasm_bindgen] is that you can pass closures defined in Rust off to JS. This can be a bit tricky at times, though, so the example here shows how to interact with some standard web APIs with closures.

src/lib.rs


# #![allow(unused_variables)]
#fn main() {
use js_sys::{Array, Date};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{Document, Element, HtmlElement, Window};

#[wasm_bindgen(start)]
pub fn run() -> Result<(), JsValue> {
    let window = web_sys::window().expect("should have a window in this context");
    let document = window.document().expect("window should have a document");

    // One of the first interesting things we can do with closures is simply
    // access stack data in Rust!
    let array = Array::new();
    array.push(&"Hello".into());
    array.push(&1.into());
    let mut first_item = None;
    array.for_each(&mut |obj, idx, _arr| match idx {
        0 => {
            assert_eq!(obj, "Hello");
            first_item = obj.as_string();
        }
        1 => assert_eq!(obj, 1),
        _ => panic!("unknown index: {}", idx),
    });
    assert_eq!(first_item, Some("Hello".to_string()));

    // Below are some more advanced usages of the `Closure` type for closures
    // that need to live beyond our function call.

    setup_clock(&window, &document)?;
    setup_clicker(&document);

    // And now that our demo is ready to go let's switch things up so
    // everything is displayed and our loading prompt is hidden.
    document
        .get_element_by_id("loading")
        .expect("should have #loading on the page")
        .dyn_ref::<HtmlElement>()
        .expect("#loading should be an `HtmlElement`")
        .style()
        .set_property("display", "none")?;
    document
        .get_element_by_id("script")
        .expect("should have #script on the page")
        .dyn_ref::<HtmlElement>()
        .expect("#script should be an `HtmlElement`")
        .style()
        .set_property("display", "block")?;

    Ok(())
}

// Set up a clock on our page and update it each second to ensure it's got
// an accurate date.
//
// Note the usage of `Closure` here because the closure is "long lived",
// basically meaning it has to persist beyond the call to this one function.
// Also of note here is the `.as_ref().unchecked_ref()` chain, which is who
// you can extract `&Function`, what `web-sys` expects, from a `Closure`
// which only hands you `&JsValue` via `AsRef`.
fn setup_clock(window: &Window, document: &Document) -> Result<(), JsValue> {
    let current_time = document
        .get_element_by_id("current-time")
        .expect("should have #current-time on the page");
    update_time(&current_time);
    let a = Closure::wrap(Box::new(move || update_time(&current_time)) as Box<dyn Fn()>);
    window
        .set_interval_with_callback_and_timeout_and_arguments_0(a.as_ref().unchecked_ref(), 1000)?;
    fn update_time(current_time: &Element) {
        current_time.set_inner_html(&String::from(
            Date::new_0().to_locale_string("en-GB", &JsValue::undefined()),
        ));
    }

    // The instances of `Closure` that we created will invalidate their
    // corresponding JS callback whenever they're dropped, so if we were to
    // normally return from `run` then both of our registered closures will
    // raise exceptions when invoked.
    //
    // Normally we'd store these handles to later get dropped at an appropriate
    // time but for now we want these to be global handlers so we use the
    // `forget` method to drop them without invalidating the closure. Note that
    // this is leaking memory in Rust, so this should be done judiciously!
    a.forget();

    Ok(())
}

// We also want to count the number of times that our green square has been
// clicked. Our callback will update the `#num-clicks` div.
//
// This is pretty similar above, but showing how closures can also implement
// `FnMut()`.
fn setup_clicker(document: &Document) {
    let num_clicks = document
        .get_element_by_id("num-clicks")
        .expect("should have #num-clicks on the page");
    let mut clicks = 0;
    let a = Closure::wrap(Box::new(move || {
        clicks += 1;
        num_clicks.set_inner_html(&clicks.to_string());
    }) as Box<dyn FnMut()>);
    document
        .get_element_by_id("green-square")
        .expect("should have #green-square on the page")
        .dyn_ref::<HtmlElement>()
        .expect("#green-square be an `HtmlElement`")
        .set_onclick(Some(a.as_ref().unchecked_ref()));
    a.forget();
}

#}

web-sys: performance.now

View full source code or view the compiled example online

Want to profile some Rust code in the browser? No problem! You can use the performance.now() API and friends to get timing information to see how long things take.

src/lib.rs


# #![allow(unused_variables)]
#fn main() {
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use wasm_bindgen::prelude::*;

// lifted from the `console_log` example
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(a: &str);
}

macro_rules! console_log {
    ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}

#[wasm_bindgen(start)]
pub fn run() {
    let window = web_sys::window().expect("should have a window in this context");
    let performance = window
        .performance()
        .expect("performance should be available");

    console_log!("the current time (in ms) is {}", performance.now());

    let start = perf_to_system(performance.timing().request_start());
    let end = perf_to_system(performance.timing().response_end());

    console_log!("request started at {}", humantime::format_rfc3339(start));
    console_log!("request ended at {}", humantime::format_rfc3339(end));
}

fn perf_to_system(amt: f64) -> SystemTime {
    let secs = (amt as u64) / 1_000;
    let nanos = ((amt as u32) % 1_000) * 1_000_000;
    UNIX_EPOCH + Duration::new(secs, nanos)
}

#}

The fetch API

View full source code or view the compiled example online

This example uses the fetch API to make an HTTP request to the GitHub API and then parses the resulting JSON.

Cargo.toml

The Cargo.toml enables a number of features related to the fetch API and types used: Headers, Request, etc. It also enables wasm-bindgen's serde support.

[package]
name = "fetch"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
futures = "0.1.20"
wasm-bindgen = { version = "0.2.29", features = ["serde-serialize"]  }
js-sys = "0.3.6"
wasm-bindgen-futures = "0.3.6"
serde = { version = "1.0.80", features = ["derive"] }
serde_derive = "^1.0.59"

[dependencies.web-sys]
version = "0.3.4"
features = [
  'Headers',
  'Request',
  'RequestInit',
  'RequestMode',
  'Response',
  'Window',
]

src/lib.rs


# #![allow(unused_variables)]
#fn main() {
use futures::{future, Future};
use js_sys::Promise;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::future_to_promise;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response};
use serde::{Deserialize, Serialize};

/// A struct to hold some data from the github Branch API.
///
/// Note how we don't have to define every member -- serde will ignore extra
/// data when deserializing
#[derive(Debug, Serialize, Deserialize)]
pub struct Branch {
    pub name: String,
    pub commit: Commit,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Commit {
    pub sha: String,
    pub commit: CommitDetails,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CommitDetails {
    pub author: Signature,
    pub committer: Signature,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Signature {
    pub name: String,
    pub email: String,
}

#[wasm_bindgen]
pub fn run() -> Promise {
    let mut opts = RequestInit::new();
    opts.method("GET");
    opts.mode(RequestMode::Cors);

    let request = Request::new_with_str_and_init(
        "https://api.github.com/repos/rustwasm/wasm-bindgen/branches/master",
        &opts,
    )
    .unwrap();

    request
        .headers()
        .set("Accept", "application/vnd.github.v3+json")
        .unwrap();

    let window = web_sys::window().unwrap();
    let request_promise = window.fetch_with_request(&request);

    let future = JsFuture::from(request_promise)
        .and_then(|resp_value| {
            // `resp_value` is a `Response` object.
            assert!(resp_value.is_instance_of::<Response>());
            let resp: Response = resp_value.dyn_into().unwrap();
            resp.json()
        })
        .and_then(|json_value: Promise| {
            // Convert this other `Promise` into a rust `Future`.
            JsFuture::from(json_value)
        })
        .and_then(|json| {
            // Use serde to parse the JSON into a struct.
            let branch_info: Branch = json.into_serde().unwrap();

            // Send the `Branch` struct back to JS as an `Object`.
            future::ok(JsValue::from_serde(&branch_info).unwrap())
        });

    // Convert this Rust `Future` back into a JS `Promise`.
    future_to_promise(future)
}

#}

2D Canvas

View full source code or view the compiled example online

Drawing a smiley face with the 2D canvas API. This is a port of part of this MDN tutorial to web-sys.

A smiley face

Cargo.toml

The Cargo.toml enables features necessary to query the DOM and work with 2D canvas.

[package]
name = "canvas"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
js-sys = "0.3.6"
wasm-bindgen = "0.2.29"

[dependencies.web-sys]
version = "0.3.4"
features = [
  'CanvasRenderingContext2d',
  'Document',
  'Element',
  'HtmlCanvasElement',
  'Window',
]

src/lib.rs

Gets the <canvas> element, creates a 2D rendering context, and draws the smiley face.


# #![allow(unused_variables)]
#fn main() {
use std::f64;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;

#[wasm_bindgen(start)]
pub fn start() {
    let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document.get_element_by_id("canvas").unwrap();
    let canvas: web_sys::HtmlCanvasElement = canvas
        .dyn_into::<web_sys::HtmlCanvasElement>()
        .map_err(|_| ())
        .unwrap();

    let context = canvas
        .get_context("2d")
        .unwrap()
        .unwrap()
        .dyn_into::<web_sys::CanvasRenderingContext2d>()
        .unwrap();

    context.begin_path();

    // Draw the outer circle.
    context
        .arc(75.0, 75.0, 50.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    // Draw the mouth.
    context.move_to(110.0, 75.0);
    context.arc(75.0, 75.0, 35.0, 0.0, f64::consts::PI).unwrap();

    // Draw the left eye.
    context.move_to(65.0, 65.0);
    context
        .arc(60.0, 65.0, 5.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    // Draw the right eye.
    context.move_to(95.0, 65.0);
    context
        .arc(90.0, 65.0, 5.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    context.stroke();
}

#}

Julia Set

View full source code or view the compiled example online

While not showing off a lot of web_sys API surface area, this example shows a neat fractal that you can make!

index.js

A small bit of glue is added for this example

import('./julia_set')
    .then(wasm => {
        const canvas = document.getElementById('drawing');
        const ctx = canvas.getContext('2d');

        const realInput = document.getElementById('real');
        const imaginaryInput = document.getElementById('imaginary');
        const renderBtn = document.getElementById('render');

        renderBtn.addEventListener('click', () => {
            const real = parseFloat(realInput.value) || 0;
            const imaginary = parseFloat(imaginaryInput.value) || 0;
            wasm.draw(ctx, 600, 600, real, imaginary);
        });

        wasm.draw(ctx, 600, 600, -0.15, 0.65);
    })
    .catch(console.error);

src/lib.rs

The bulk of the logic is in the generation of the fractal


# #![allow(unused_variables)]
#fn main() {
use std::ops::Add;
use wasm_bindgen::prelude::*;
use wasm_bindgen::Clamped;
use web_sys::{CanvasRenderingContext2d, ImageData};

#[wasm_bindgen]
pub fn draw(
    ctx: &CanvasRenderingContext2d,
    width: u32,
    height: u32,
    real: f64,
    imaginary: f64,
) -> Result<(), JsValue> {
    // The real workhorse of this algorithm, generating pixel data
    let c = Complex { real, imaginary };
    let mut data = get_julia_set(width, height, c);
    let data = ImageData::new_with_u8_clamped_array_and_sh(Clamped(&mut data), width, height)?;
    ctx.put_image_data(&data, 0.0, 0.0)
}

fn get_julia_set(width: u32, height: u32, c: Complex) -> Vec<u8> {
    let mut data = Vec::new();

    let param_i = 1.5;
    let param_r = 1.5;
    let scale = 0.005;

    for x in 0..width {
        for y in 0..height {
            let z = Complex {
                real: y as f64 * scale - param_r,
                imaginary: x as f64 * scale - param_i,
            };
            let iter_index = get_iter_index(z, c);
            data.push((iter_index / 4) as u8);
            data.push((iter_index / 2) as u8);
            data.push(iter_index as u8);
            data.push(255);
        }
    }

    data
}

fn get_iter_index(z: Complex, c: Complex) -> u32 {
    let mut iter_index: u32 = 0;
    let mut z = z;
    while iter_index < 900 {
        if z.norm() > 2.0 {
            break;
        }
        z = z.square() + c;
        iter_index += 1;
    }
    iter_index
}

#[derive(Clone, Copy, Debug)]
struct Complex {
    real: f64,
    imaginary: f64,
}

impl Complex {
    fn square(self) -> Complex {
        let real = (self.real * self.real) - (self.imaginary * self.imaginary);
        let imaginary = 2.0 * self.real * self.imaginary;
        Complex { real, imaginary }
    }

    fn norm(&self) -> f64 {
        (self.real * self.real) + (self.imaginary * self.imaginary)
    }
}

impl Add<Complex> for Complex {
    type Output = Complex;

    fn add(self, rhs: Complex) -> Complex {
        Complex {
            real: self.real + rhs.real,
            imaginary: self.imaginary + rhs.imaginary,
        }
    }
}

#}

WebAudio

View full source code or view the compiled example online

This example creates an FM oscillator using the WebAudio API and web-sys.

Cargo.toml

The Cargo.toml enables the types needed to use the relevant bits of the WebAudio API.

[package]
name = "webaudio"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2.29"

[dependencies.web-sys]
version = "0.3.4"
features = [
  'AudioContext',
  'AudioDestinationNode',
  'AudioNode',
  'AudioParam',
  'GainNode',
  'OscillatorNode',
  'OscillatorType',
]

src/lib.rs

The Rust code implements the FM oscillator.


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;
use web_sys::{AudioContext, OscillatorType};

/// Converts a midi note to frequency
///
/// A midi note is an integer, generally in the range of 21 to 108
pub fn midi_to_freq(note: u8) -> f32 {
    27.5 * 2f32.powf((note as f32 - 21.0) / 12.0)
}

#[wasm_bindgen]
pub struct FmOsc {
    ctx: AudioContext,
    /// The primary oscillator.  This will be the fundamental frequency
    primary: web_sys::OscillatorNode,

    /// Overall gain (volume) control
    gain: web_sys::GainNode,

    /// Amount of frequency modulation
    fm_gain: web_sys::GainNode,

    /// The oscillator that will modulate the primary oscillator's frequency
    fm_osc: web_sys::OscillatorNode,

    /// The ratio between the primary frequency and the fm_osc frequency.
    ///
    /// Generally fractional values like 1/2 or 1/4 sound best
    fm_freq_ratio: f32,

    fm_gain_ratio: f32,
}

impl Drop for FmOsc {
    fn drop(&mut self) {
        let _ = self.ctx.close();
    }
}

#[wasm_bindgen]
impl FmOsc {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Result<FmOsc, JsValue> {
        let ctx = web_sys::AudioContext::new()?;

        // Create our web audio objects.
        let primary = ctx.create_oscillator()?;
        let fm_osc = ctx.create_oscillator()?;
        let gain = ctx.create_gain()?;
        let fm_gain = ctx.create_gain()?;

        // Some initial settings:
        primary.set_type(OscillatorType::Sine);
        primary.frequency().set_value(440.0); // A4 note
        gain.gain().set_value(0.0); // starts muted
        fm_gain.gain().set_value(0.0); // no initial frequency modulation
        fm_osc.set_type(OscillatorType::Sine);
        fm_osc.frequency().set_value(0.0);

        // Connect the nodes up!

        // The primary oscillator is routed through the gain node, so that
        // it can control the overall output volume.
        primary.connect_with_audio_node(&gain)?;

        // Then connect the gain node to the AudioContext destination (aka
        // your speakers).
        gain.connect_with_audio_node(&ctx.destination())?;

        // The FM oscillator is connected to its own gain node, so it can
        // control the amount of modulation.
        fm_osc.connect_with_audio_node(&fm_gain)?;

        // Connect the FM oscillator to the frequency parameter of the main
        // oscillator, so that the FM node can modulate its frequency.
        fm_gain.connect_with_audio_param(&primary.frequency())?;

        // Start the oscillators!
        primary.start()?;
        fm_osc.start()?;

        Ok(FmOsc {
            ctx,
            primary,
            gain,
            fm_gain,
            fm_osc,
            fm_freq_ratio: 0.0,
            fm_gain_ratio: 0.0,
        })
    }

    /// Sets the gain for this oscillator, between 0.0 and 1.0.
    #[wasm_bindgen]
    pub fn set_gain(&self, mut gain: f32) {
        if gain > 1.0 {
            gain = 1.0;
        }
        if gain < 0.0 {
            gain = 0.0;
        }
        self.gain.gain().set_value(gain);
    }

    #[wasm_bindgen]
    pub fn set_primary_frequency(&self, freq: f32) {
        self.primary.frequency().set_value(freq);

        // The frequency of the FM oscillator depends on the frequency of the
        // primary oscillator, so we update the frequency of both in this method.
        self.fm_osc.frequency().set_value(self.fm_freq_ratio * freq);
        self.fm_gain.gain().set_value(self.fm_gain_ratio * freq);
    }

    #[wasm_bindgen]
    pub fn set_note(&self, note: u8) {
        let freq = midi_to_freq(note);
        self.set_primary_frequency(freq);
    }

    /// This should be between 0 and 1, though higher values are accepted.
    #[wasm_bindgen]
    pub fn set_fm_amount(&mut self, amt: f32) {
        self.fm_gain_ratio = amt;

        self.fm_gain
            .gain()
            .set_value(self.fm_gain_ratio * self.primary.frequency().value());
    }

    /// This should be between 0 and 1, though higher values are accepted.
    #[wasm_bindgen]
    pub fn set_fm_frequency(&mut self, amt: f32) {
        self.fm_freq_ratio = amt;
        self.fm_osc
            .frequency()
            .set_value(self.fm_freq_ratio * self.primary.frequency().value());
    }
}

#}

index.js

A small bit of JavaScript glues the rust module to input widgets and translates events into calls into wasm code.

import('./webaudio')
  .then(rust_module => {
    let fm = null;

    const play_button = document.getElementById("play");
    play_button.addEventListener("click", event => {
      if (fm === null) {
        fm = new rust_module.FmOsc();
        fm.set_note(50);
        fm.set_fm_frequency(0);
        fm.set_fm_amount(0);
        fm.set_gain(0.8);
      } else {
        fm.free();
        fm = null;
      }
    });

    const primary_slider = document.getElementById("primary_input");
    primary_slider.addEventListener("input", event => {
      if (fm) {
        fm.set_note(event.target.value);
      }
    });

    const fm_freq = document.getElementById("fm_freq");
    fm_freq.addEventListener("input", event => {
      if (fm) {
        fm.set_fm_frequency(event.target.value);
      }
    });

    const fm_amount = document.getElementById("fm_amount");
    fm_amount.addEventListener("input", event => {
      if (fm) {
        fm.set_fm_amount(event.target.value);
      }
    });
  })
  .catch(console.error);

WebGL Example

View full source code or view the compiled example online

This example draws a triangle to the screen using the WebGL API.

Cargo.toml

The Cargo.toml enables features necessary to obtain and use a WebGL rendering context.

[package]
name = "webgl"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
js-sys = "0.3.6"
wasm-bindgen = "0.2.29"

[dependencies.web-sys]
version = "0.3.4"
features = [
  'Document',
  'Element',
  'HtmlCanvasElement',
  'WebGlBuffer',
  'WebGlRenderingContext',
  'WebGlProgram',
  'WebGlShader',
  'Window',
]

src/lib.rs

This source file handles all of the necessary logic to obtain a rendering context, compile shaders, fill a buffer with vertex coordinates, and draw a triangle to the screen.


# #![allow(unused_variables)]
#fn main() {
use js_sys::WebAssembly;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{WebGlProgram, WebGlRenderingContext, WebGlShader};

#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
    let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document.get_element_by_id("canvas").unwrap();
    let canvas: web_sys::HtmlCanvasElement = canvas.dyn_into::<web_sys::HtmlCanvasElement>()?;

    let context = canvas
        .get_context("webgl")?
        .unwrap()
        .dyn_into::<WebGlRenderingContext>()?;

    let vert_shader = compile_shader(
        &context,
        WebGlRenderingContext::VERTEX_SHADER,
        r#"
        attribute vec4 position;
        void main() {
            gl_Position = position;
        }
    "#,
    )?;
    let frag_shader = compile_shader(
        &context,
        WebGlRenderingContext::FRAGMENT_SHADER,
        r#"
        void main() {
            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
        }
    "#,
    )?;
    let program = link_program(&context, [vert_shader, frag_shader].iter())?;
    context.use_program(Some(&program));

    let vertices: [f32; 9] = [-0.7, -0.7, 0.0, 0.7, -0.7, 0.0, 0.0, 0.7, 0.0];
    let memory_buffer = wasm_bindgen::memory()
        .dyn_into::<WebAssembly::Memory>()?
        .buffer();
    let vertices_location = vertices.as_ptr() as u32 / 4;
    let vert_array = js_sys::Float32Array::new(&memory_buffer)
        .subarray(vertices_location, vertices_location + vertices.len() as u32);

    let buffer = context.create_buffer().ok_or("failed to create buffer")?;
    context.bind_buffer(WebGlRenderingContext::ARRAY_BUFFER, Some(&buffer));
    context.buffer_data_with_array_buffer_view(
        WebGlRenderingContext::ARRAY_BUFFER,
        &vert_array,
        WebGlRenderingContext::STATIC_DRAW,
    );
    context.vertex_attrib_pointer_with_i32(0, 3, WebGlRenderingContext::FLOAT, false, 0, 0);
    context.enable_vertex_attrib_array(0);

    context.clear_color(0.0, 0.0, 0.0, 1.0);
    context.clear(WebGlRenderingContext::COLOR_BUFFER_BIT);

    context.draw_arrays(
        WebGlRenderingContext::TRIANGLES,
        0,
        (vertices.len() / 3) as i32,
    );
    Ok(())
}

pub fn compile_shader(
    context: &WebGlRenderingContext,
    shader_type: u32,
    source: &str,
) -> Result<WebGlShader, String> {
    let shader = context
        .create_shader(shader_type)
        .ok_or_else(|| String::from("Unable to create shader object"))?;
    context.shader_source(&shader, source);
    context.compile_shader(&shader);

    if context
        .get_shader_parameter(&shader, WebGlRenderingContext::COMPILE_STATUS)
        .as_bool()
        .unwrap_or(false)
    {
        Ok(shader)
    } else {
        Err(context
            .get_shader_info_log(&shader)
            .unwrap_or_else(|| "Unknown error creating shader".into()))
    }
}

pub fn link_program<'a, T: IntoIterator<Item = &'a WebGlShader>>(
    context: &WebGlRenderingContext,
    shaders: T,
) -> Result<WebGlProgram, String> {
    let program = context
        .create_program()
        .ok_or_else(|| String::from("Unable to create shader object"))?;
    for shader in shaders {
        context.attach_shader(&program, shader)
    }
    context.link_program(&program);

    if context
        .get_program_parameter(&program, WebGlRenderingContext::LINK_STATUS)
        .as_bool()
        .unwrap_or(false)
    {
        Ok(program)
    } else {
        Err(context
            .get_program_info_log(&program)
            .unwrap_or_else(|| "Unknown error creating program object".into()))
    }
}

#}

web-sys: A requestAnimationFrame Loop

View full source code or view the compiled example online

This is an example of a requestAnimationFrame loop using the web-sys crate! It renders a count of how many times a requestAnimationFrame callback has been invoked and then it breaks out of the requestAnimationFrame loop after 300 iterations.

Cargo.toml

You can see here how we depend on web-sys and activate associated features to enable all the various APIs:

[package]
name = "request-animation-frame"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2.29"

[dependencies.web-sys]
version = "0.3.4"
features = [
  'Document',
  'Element',
  'HtmlElement',
  'Node',
  'Window',
]

src/lib.rs


# #![allow(unused_variables)]
#fn main() {
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;

fn window() -> web_sys::Window {
    web_sys::window().expect("no global `window` exists")
}

fn request_animation_frame(f: &Closure<FnMut()>) {
    window()
        .request_animation_frame(f.as_ref().unchecked_ref())
        .expect("should register `requestAnimationFrame` OK");
}

fn document() -> web_sys::Document {
    window()
        .document()
        .expect("should have a document on window")
}

fn body() -> web_sys::HtmlElement {
    document().body().expect("document should have a body")
}

// This function is automatically invoked after the wasm module is instantiated.
#[wasm_bindgen(start)]
pub fn run() -> Result<(), JsValue> {
    let f = Rc::new(RefCell::new(None));
    let g = f.clone();

    {
        let mut i = 0;
        *g.borrow_mut() = Some(Closure::wrap(Box::new(move || {
            if i > 300 {
                body().set_text_content(Some("All done!"));

                // Drop our handle to this closure so that it will get cleaned
                // up once we return.
                let _ = f.borrow_mut().take();
                return;
            }

            // Set the body's text content to how many times this
            // requestAnimationFrame callback has fired.
            i += 1;
            let text = format!("requestAnimationFrame has been called {} times.", i);
            body().set_text_content(Some(&text));

            // Schedule ourself for another requestAnimationFrame callback.
            request_animation_frame(f.borrow().as_ref().unwrap());
        }) as Box<FnMut()>));
    }

    request_animation_frame(g.borrow().as_ref().unwrap());
    Ok(())
}

#}

Paint Example

View full source code or view the compiled example online

A simple painting program.

Cargo.toml

The Cargo.toml enables features necessary to work with the DOM, events and 2D canvas.

[package]
name = "wasm-bindgen-paint"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
js-sys = "0.3.6"
wasm-bindgen = "0.2.29"

[dependencies.web-sys]
version = "0.3.4"
features = [
  'CanvasRenderingContext2d',
  'CssStyleDeclaration',
  'Document',
  'Element',
  'EventTarget',
  'HtmlCanvasElement',
  'HtmlElement',
  'MouseEvent',
  'Node',
  'Window',
]

src/lib.rs

Creates the <canvas> element, applies a CSS style to it, adds it to the document, get a 2D rendering context and adds listeners for mouse events.


# #![allow(unused_variables)]
#fn main() {
use std::cell::Cell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;

#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
    let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document
        .create_element("canvas")?
        .dyn_into::<web_sys::HtmlCanvasElement>()?;
    document.body().unwrap().append_child(&canvas)?;
    canvas.set_width(640);
    canvas.set_height(480);
    canvas.style().set_property("border", "solid")?;
    let context = canvas
        .get_context("2d")?
        .unwrap()
        .dyn_into::<web_sys::CanvasRenderingContext2d>()?;
    let context = Rc::new(context);
    let pressed = Rc::new(Cell::new(false));
    {
        let context = context.clone();
        let pressed = pressed.clone();
        let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
            context.begin_path();
            context.move_to(event.offset_x() as f64, event.offset_y() as f64);
            pressed.set(true);
        }) as Box<dyn FnMut(_)>);
        canvas.add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref())?;
        closure.forget();
    }
    {
        let context = context.clone();
        let pressed = pressed.clone();
        let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
            if pressed.get() {
                context.line_to(event.offset_x() as f64, event.offset_y() as f64);
                context.stroke();
                context.begin_path();
                context.move_to(event.offset_x() as f64, event.offset_y() as f64);
            }
        }) as Box<dyn FnMut(_)>);
        canvas.add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref())?;
        closure.forget();
    }
    {
        let context = context.clone();
        let pressed = pressed.clone();
        let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
            pressed.set(false);
            context.line_to(event.offset_x() as f64, event.offset_y() as f64);
            context.stroke();
        }) as Box<dyn FnMut(_)>);
        canvas.add_event_listener_with_callback("mouseup", closure.as_ref().unchecked_ref())?;
        closure.forget();
    }

    Ok(())
}

#}

Parallel Raytracing

View full source code or view the compiled example online

This is an unstable and experimental example of using threads with WebAssembly and Rust, culminating in a parallel raytracer demo. Current requirements for viewing this demo are:

  • Firefox Nightly - other browsers haven't implemented the proposed WebAssembly features yet.
  • SharedArrayBuffer is enabled in about:config in Firefox

This demo may also break over time as Firefox updates, but we'll try to keep it working!

Locally to build this demo you'll need xargo and the rust-src rustup component, and afterwards ./build.sh like other examples should build the example.

Again, to reiterate, this is all experimental and we're working through various issues as we're working on this. If you're curious to see how this works it's best to explore via the source code right now! More info will be available here once WebAssembly threads are closer to stabilization.

TODO MVC using wasm-bingen and web-sys

View full source code or view the compiled example online

wasm-bindgen and web-sys coded TODO MVC

The code was rewritten from the ES6 version.

The core differences are:

  • Having an Element wrapper that takes care of dyn and into refs in web-sys,
  • A Scheduler that allows Controller and View to communicate to each other by emulating something similar to the JS event loop.

Size

The size of the project hasn't undergone much work to make it optimised yet.

  • ~96kb release build
  • ~76kb optimised with binaryen
  • ~28kb brotli compressed

Reference

This section contains reference material for using wasm-bindgen. It is not intended to be read start to finish. Instead, it aims to quickly answer questions like:

  • Is type X supported as a parameter in a Rust function exported to JavaScript?

  • What was that CLI flag to disable ECMAScript modules output, and instead attach the JavaScript bindings directly to window?

Passing Rust Closures to Imported JavaScript Functions

The #[wasm_bindgen] attribute supports Rust closures being passed to JavaScript in two variants:

  1. Stack-lifetime closures that should not be invoked by JavaScript again after the imported JavaScript function that the closure was passed to returns.

  2. Heap-allocated closures that can be invoked any number of times, but must be explicitly deallocated when finished.

Stack-Lifetime Closures

Closures with a stack lifetime are passed to JavaScript as either &Fn or &mut FnMut trait objects:


# #![allow(unused_variables)]
#fn main() {
// Import JS functions that take closures

#[wasm_bindgen]
extern "C" {
    fn takes_immutable_closure(f: &Fn());

    fn takes_mutable_closure(f: &mut FnMut());
}

// Usage

takes_immutable_closure(&|| {
    // ...
});

let mut times_called = 0;
takes_mutable_closure(&mut || {
    times_called += 1;
});
#}

Once these imported functions return, the closures that were given to them will become invalidated, and any future attempts to call those closures from JavaScript will raise an exception.

Closures also support arguments and return values like exports do, for example:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    fn takes_closure_that_takes_int_and_returns_string(x: &Fn(u32) -> String);
}

takes_closure_that_takes_int_and_returns_string(&|x: u32| -> String {
    format!("x is {}", x)
});
#}

Heap-Allocated Closures

Sometimes the discipline of stack-lifetime closures is not desired. For example, you'd like to schedule a closure to be run on the next turn of the event loop in JavaScript through setTimeout. For this, you want the imported function to return but the JavaScript closure still needs to be valid!

For this scenario, you need the Closure type, which is defined in the wasm_bindgen crate, exported in wasm_bindgen::prelude, and represents a "long lived" closure. The Closure type is currently behind a feature which needs to be enabled:

[dependencies]
wasm-bindgen = {version = "^0.2", features = ["nightly"]}

The validity of the JavaScript closure is tied to the lifetime of the Closure in Rust. Once a Closure is dropped, it will deallocate its internal memory and invalidate the corresponding JavaScript function so that any further attempts to invoke it raise an exception.

Like stack closures a Closure supports both Fn and FnMut closures, as well as arguments and returns.


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    fn setInterval(closure: &Closure<FnMut()>, millis: u32) -> f64;
    fn cancelInterval(token: f64);

    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen]
pub struct Interval {
    closure: Closure<FnMut()>,
    token: f64,
}

impl Interval {
    pub fn new<F>(millis: u32, f: F) -> Interval
    where
        F: FnMut()
    {
        // Construct a new closure.
        let closure = Closure::new(f);

        // Pass the closuer to JS, to run every n milliseconds.
        let token = setInterval(&closure, millis);

        Interval { closure, token }
    }
}

// When the Interval is destroyed, cancel its `setInterval` timer.
impl Drop for Interval {
    fn drop(&mut self) {
        cancelInterval(self.token);
    }
}

// Keep logging "hello" every second until the resulting `Interval` is dropped.
#[wasm_bindgen]
pub fn hello() -> Interval {
    Interval::new(1_000, || log("hello"));
}
#}

Receiving JavaScript Closures in Exported Rust Functions

You can use the js-sys crate to access JavaScript's Function type, and invoke that function via Function.prototype.apply and Function.prototype.call.

For example, we can wrap a Vec<u32> in a new type, export it to JavaScript, and invoke a JavaScript closure on each member of the Vec:


# #![allow(unused_variables)]
#fn main() {
extern crate js_sys;
extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct VecU32 {
    xs: Vec<u32>,
}

#[wasm_bindgen]
impl VecU32 {
    pub fn each(&self, f: &js_sys::Function) {
        let this = JsValue::NULL;
        for x in &self.xs {
            let x = JsValue::from(x);
            let _ = f.call1(&this, &x);
        }
    }
}
#}

Since Rust has no function overloading, the call# method also requires a number representing the amount of arguments passed to the JavaScript closure.

Converting Between JavaScript Promises and Rust Futures

The wasm-bindgen-futures crate bridges the gap between JavaScript Promises and Rust Futures. Its JsFuture type provides conversion from a JavaScript Promise into a Rust Future, and its future_to_promise function converts a Rust Future into a JavaScript Promise and schedules it to be driven to completion.

Learn more:

Iterating over JavaScript Values

Methods That Return js_sys::Iterator

Some JavaScript collections have methods for iterating over their values or keys:

These methods return js_sys::Iterator, which is the Rust representation of a JavaScript object that has a next method that either returns the next item in the iteration, notes that iteration has completed, or throws an error. That is, js_sys::Iterator represents an object that implements the duck-typed JavaScript iteration protocol.

js_sys::Iterator can be converted into a Rust iterator either by reference (into js_sys::Iter<'a>) or by value (into js_sys::IntoIter). The Rust iterator will yield items of type Result<JsValue>. If it yields an Ok(...), then the JS iterator protocol returned an element. If it yields an Err(...), then the JS iterator protocol threw an exception.


# #![allow(unused_variables)]
#fn main() {
extern crate js_sys;
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn count_strings_in_set(set: &js_sys::Set) -> u32 {
    let mut count = 0;

    // Call `keys` to get an iterator over the set's elements. Because this is
    // in a `for ... in ...` loop, Rust will automatically call its
    // `IntoIterator` trait implementation to convert it into a Rust iterator.
    for x in set.keys() {
        // We know the built-in iterator for set elements won't throw
        // exceptions, so just unwrap the element. If this was an untrusted
        // iterator, we might want to explicitly handle the case where it throws
        // an exception instead of returning a `{ value, done }` object.
        let x = x.unwrap();

        // If `x` is a string, increment our count of strings in the set!
        if x.is_string() {
            count += 1;
        }
    }

    count
}
#}

Iterating Over Any JavaScript Object that Implements the Iterator Protocol

You could manually test for whether an object implements JS's duck-typed iterator protocol, and if so, convert it into a js_sys::Iterator that you can finally iterate over. You don't need to do this by-hand, however, since we bundled this up as the js_sys::try_iter function!

For example, we can write a function that collects the numbers from any JS iterable and returns them as an Array:


# #![allow(unused_variables)]
#fn main() {
extern crate js_sys;
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn collect_numbers(some_iterable: &JsValue) -> Result<js_sys::Array, JsValue> {
    let nums = js_sys::Array::new();

    let iterator = js_sys::try_iter(some_iterable)?.ok_or_else(|| {
        "need to pass iterable JS values!"
    })?;

    for x in iterator {
        // If the iterator's `next` method throws an error, propagate it
        // up to the caller.
        let x = x?;

        // If `x` is a number, add it to our array of numbers!
        if x.as_f64().is_some() {
            nums.push(&x);
        }
    }

    Ok(nums)
}
#}

No ES Modules

Explained a bit more in the internal design section one of the key foundational principles of wasm-bindgen is ES modules. It supports working without ES modules, however! Not all JS tooling and browsers are ready for ES modules by default, so it can sometimes be helpful to quickly get up and running without them to kick the tires and see how wasm-bindgen works.

Let's start out with our hello-world example from previous chapters, and you can also follow along in the repository.


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    fn alert(msg: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    alert(&format!("Hello, {}!", name);
}
#}

Like usual, we first compile this to wasm:

$ cargo build --target wasm32-unknown-unknown

Next, to avoid using ES modules, pass the --no-modules option to the wasm-bindgen command:

$ wasm-bindgen target/wasm32-unknown-unknown/debug/hello.wasm --no-modules --out-dir .

Next up we need to write some HTML to interact with the wasm:

<html>
  <body>
    <script src='./hello.js'></script>
    <script>
      wasm_bindgen('./hello_bg.wasm')
        .then(() => wasm_bindgen.greet('World'));
    </script>
  </body>
</html>

and that's it! If you open up that web page in a browser (needs to be over HTTP) then you should see an alert for "Hello, World!".

The --no-modules output will not instantiate or compile the wasm module when included on a web page, instead it just parses and configures the JS bindings for the wasm-module-to-be. The page is configured with one exported global, in this case wasm_bindgen. The name of this global can be configured with the --no-modules-global option.

The global wasm_bindgen is a function that takes one argument: either the path to the wasm file to fetch or a WebAssembly.Module. When invoked wasm_bindgen will return a promise for when the wasm module is ready-to-go. After that all exported functionality on wasm_bindgen will be functional.

In the example above, after calling wasm_bindgen('./hello_bg.wasm') we wait for the wasm module to be fetched and compiled, and afterwards we're invoking our greet export.

Note that exports are available for binding before the wasm module has been instantiated, for example this would have also worked:

const { greet } = wasm_bindgen;

wasm_bindgen('./hello_bg.wasm')
  .then(() => greet('World'));

Serializing and Deserializing Arbitrary Data Into and From JsValue with Serde

It's possible to pass arbirtrary data from Rust to JavaScript by serializing it with Serde. wasm-bindgen includes the JsValue type, which streamlines serializing and deserializing.

Enable the "serde-serialize" Feature

To enable the "serde-serialize" feature, do two things in Cargo.toml:

  1. Add the serde and serde_derive crates to [dependencies].
  2. Add features = ["serde-serialize"] to the existing wasm-bindgen dependency.
[dependencies]
serde = "^1.0.59"
serde_derive = "^1.0.59"

[dependencies.wasm-bindgen]
version = "^0.2"
features = ["serde-serialize"]

Import Serde's Custom-Derive Macros

In your top-level Rust file (e.g. lib.rs or main.rs), enable the Serialize and Deserialize custom-derive macros:


# #![allow(unused_variables)]
#fn main() {
#[macro_use]
extern crate serde_derive;
#}

Derive the Serialize and Deserialize Traits

Add #![derive(Serialize, Deserialize)] to your type. All of your type's members must also be supported by Serde, i.e. their types must also implement the Serialize and Deserialize traits.

For example, let's say we'd like to pass this struct to JavaScript; doing so is not possible in wasm-bindgen normally due to the use of HashMaps, arrays, and nested Vecs. None of those types are supported for sending across the wasm ABI naively, but all of them implement Serde's Serialize and Deserialize.

Note that we do not need to use the #[wasm_bindgen] macro.


# #![allow(unused_variables)]
#fn main() {
#[derive(Serialize)]
pub struct Example {
    pub field1: HashMap<u32, String>,
    pub field2: Vec<Vec<f32>>,
    pub field3: [f32; 4],
}
#}

Send it to JavaScript with JsValue::from_serde

Here's a function that will pass an Example to JavaScript by serializing it to JsValue:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
pub fn send_example_to_js() -> JsValue {
    let mut field1 = HashMap::new();
    field1.insert(0, String::from("ex"));
    let example = Example {
        field1,
        field2: vec![vec![1., 2.], vec![3., 4.]],
        field3: [1., 2., 3., 4.]
    };

    JsValue::from_serde(&example).unwrap()
}
#}

Receive it from JavaScript with JsValue::into_serde

Here's a function that will receive a JsValue parameter from JavaScript and then deserialize an Example from it:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
pub fn receive_example_from_js(val: &JsValue) {
    let example: Example = val.into_serde().unwrap();
    ...
}
#}

JavaScript Usage

In the JsValue that JavaScript gets, field1 will be an Object (not a JavaScript Map), field2 will be a JavaScript Array whose members are Arrays of numbers, and field3 will be an Array of numbers.

import { send_example_to_js, receive_example_from_js } from "example";

// Get the example object from wasm.
let example = send_example_to_js();

// Add another "Vec" element to the end of the "Vec<Vec<f32>>"
example.field2.push([5,6]);

// Send the example object back to wasm.
receive_example_from_js(example);

Accessing Properties of Untyped JavaScript Values

To read and write arbitrary properties from any untyped JavaScript value regardless if it is an instanceof some JavaScript class or not, use the js_sys::Reflect APIs. These APIs are bindings to the JavaScript builtin Reflect object and its methods.

You might also benefit from using duck-typed interfaces instead of working with untyped values.

Reading Properties with js_sys::Reflect::get

API documentation for js_sys::Reflect::get.

A function that returns the value of a property.

Rust Usage


# #![allow(unused_variables)]
#fn main() {
let value = js_sys::Reflect::get(&target, &property_key)?;
#}

JavaScript Equivalent

let value = target[property_key];

Writing Properties with js_sys::Reflect::set

API documentation for js_sys::Reflect::set.

A function that assigns a value to a property. Returns a boolean that is true if the update was successful.

Rust Usage


# #![allow(unused_variables)]
#fn main() {
js_sys::Reflect::set(&target, &property_key, &value)?;
#}

JavaScript Equivalent

target[property_key] = value;

Determining if a Property Exists with js_sys::Reflect::has

API documentation for js_sys::Reflect::has.

The JavaScript in operator as function. Returns a boolean indicating whether an own or inherited property exists on the target.

Rust Usage


# #![allow(unused_variables)]
#fn main() {
if js_sys::Reflect::has(&target, &property_key)? {
    // ...
} else {
    // ...
}
#}

JavaScript Equivalent

if (property_key in target) {
    // ...
} else {
    // ...
}

But wait — there's more!

See the js_sys::Reflect API documentation for the full listing of JavaScript value reflection and introspection capabilities.

Working with Duck-Typed Interfaces

Liberal use of the structural attribute on imported methods, getters, and setters allows you to define duck-typed interfaces. A duck-typed interface is one where many different JavaScript objects that don't share the same base class in their prototype chain and therefore are not instanceof the same base can be used the same way.

Defining a Duck-Typed Interface in Rust


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

/// Here is a duck-typed interface for any JavaScript object that has a `quack`
/// method.
///
/// Note that any attempts to check if an object is a `Quacks` with
/// `JsCast::is_instance_of` (i.e. the `instanceof` operator) will fail because
/// there is no JS class named `Quacks`.
#[wasm_bindgen]
extern "C" {
    pub type Quacks;

    #[wasm_bindgen(structural, method)]
    pub fn quack(this: &Quacks) -> String;
}

/// Next, we can export a function that takes any object that quacks:
#[wasm_bindgen]
pub fn make_em_quack_to_this(duck: &Quacks) {
    let _s = duck.quack();
    // ...
}

#}

JavaScript Usage

import { make_em_quack_to_this } from "./rust_duck_typed_interfaces";

// All of these objects implement the `Quacks` interface!

const alex = {
  quack: () => "you're not wrong..."
};

const ashley = {
  quack: () => "<corgi.gif>"
};

const nick = {
  quack: () => "rappers I monkey-flip em with the funky rhythm I be kickin"
};

// Get all our ducks in a row and call into wasm!

make_em_quack_to_this(alex);
make_em_quack_to_this(ashley);
make_em_quack_to_this(nick);

The wasm-bindgen Command Line Interface

The wasm-bindgen command line tool has a number of options available to it to tweak the JavaScript that is generated. The most up-to-date set of flags can always be listed via wasm-bindgen --help.

Note: usually, one should use a wasm-pack-based workflow rather than running the wasm-bindgen command line tool by hand.

Usage

wasm-bindgen [options] ./target/wasm32-unknown-unknown/release/crate.wasm

Options

--out-dir DIR

The target directory to emit the JavaScript bindings, TypeScript definitions, processed .wasm binary, etc...

--nodejs

This flag will tailor output for Node instead of browsers, allowing for native usage of require of the generated JS and internally using require instead of ECMAScript modules. When using this flag no further postprocessing (aka a bundler) should be necessary to work with the wasm.

--browser

This flag will tailor the output specifically for browsers, making it incompatible with Node. This will basically make the generated JS a tiny bit smaller as runtime checks for Node won't be necessary.

--no-modules and --no-modules-global VAR

The default output of wasm-bindgen uses ECMAScript modules. These options indicate that ECMAScript modules should not be used, and that output should be tailored for a properties on the JavaScript global object (e.g. window).

The --no-modules-global VAR option makes VAR the global property that the JavaScript bindings are attached to.

More information can be found in the documentation for building without ECMAScript modules.

--typescript

Output a TypeScript declaration file for the generated JavaScript bindings. This is on by default.

--no-typescript

By default, a *.d.ts TypeScript declaration file is generated for the generated JavaScript bindings, but this flag will disable that.

--debug

Generates a bit more JS and wasm in "debug mode" to help catch programmer errors, but this output isn't intended to be shipped to production.

--no-demangle

When post-processing the .wasm binary, do not demangle Rust symbols in the "names" custom section.

--keep-debug

When post-processing the .wasm binary, do not strip DWARF debug info custom sections.

Optimizing for Size with wasm-bindgen

The Rust and WebAssembly Working Group's Game of Life tutorial has an excellent section on shrinking wasm code size, but there's a few wasm-bindgen-specific items to mention as well!

First and foremost, wasm-bindgen is designed to be lightweight and a "pay only for what you use" mentality. If you suspect that wasm-bindgen is bloating your program that is a bug and we'd like to know about it! Please feel free to file an issue, even if it's a question!

What to profile

With wasm-bindgen there's a few different files to be measuring the size of. The first of which is the output of the compiler itself, typically at target/wasm32-unknown-unknown/release/foo.wasm. This file is not optimized for size and you should not measure it. The output of the compiler when linking with wasm-bindgen is by design larger than it needs to be, the wasm-bindgen CLI tool will automatically strip all unneeded functionality out of the binary.

This leaves us with two primary generated files to measure the size of:

  • Generated wasm - after running the wasm-bindgen CLI tool you'll get a file in --out-dir that looks like foo_bg.wasm. This file is the final fully-finished artifact from wasm-bindgen, and it reflects the size of the app you'll be publishing. All the optimizations mentioned in the code size tutorial will help reduce the size of this binary, so feel free to go crazy!

  • Generated JS - the other file after running wasm-bindgen is a foo.js file which is what's actually imported by other JS code. This file is already generated to be as small as possible (not including unneeded functionality). The JS, however, is not uglified or minified, but rather still human readable and debuggable. It's expected that you'll run an uglifier or bundler of the JS output to minimize it further in your application. If you spot a way we could reduce the output JS size further (or make it more amenable to bundler minification), please let us know!

Example

As an example, the wasm-bindgen repository contains an example about generating small wasm binaries and shows off how to generate a small wasm file for adding two numbers.

Supported Rust Targets

Note: This section is about Rust target triples, not targets like node/web workers/browsers. More information on that coming soon!

The wasm-bindgen project is designed to target the wasm32-unknown-unknown target in Rust. This target is a "bare bones" target for Rust which emits WebAssembly as output. The standard library is largely inert as modules like std::fs and std::net will simply return errors.

Non-wasm targets

Note that wasm-bindgen also aims to compile on all targets. This means that it should be safe, if you like, to use #[wasm_bindgen] even when compiling for Windows (for example). For example:

#[wasm_bindgen]
pub fn add(a: u32, b: u32) -> u32 {
    a + b
}

#[cfg(not(target_arch = "wasm32"))]
fn main() {
    println!("1 + 2 = {}", add(1, 2));
}

This program will compile and work on all platforms, not just wasm32-unknown-unknown. Note that imported functions with #[wasm_bindgen] will unconditionally panic on non-wasm targets. For example:

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

fn main() {
    log("hello!");
}

This program will unconditionally panic on all platforms other than wasm32-unknown-unknown.

For better compile times, however, you likely want to only use #[wasm_bindgen] on the wasm32-unknown-unknown target. You can have a target-specific dependency like so:

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2"

And in your code you can use:


# #![allow(unused_variables)]
#fn main() {
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn only_on_the_wasm_target() {
    // ...
}
#}

Other Web Targets

The wasm-bindgen target does not support the wasm32-unknown-emscripten nor the asmjs-unknown-emscripten targets. There are currently no plans to support these targets either. All annotations work like other platforms on the targets, retaining exported functions and causing all imports to panic.

Supported Browsers

The output of wasm-bindgen includes a JS file, and as a result it's good to know what browsers that file is expected to be used in! By default the output uses ES modules which isn't implemented in all browsers today, but when using a bundler (like Webpack) you should be able to produce output suitable for all browsers.

Firefox, Chrome, Safari, and Edge browsers are all supported by wasm-bindgen. If you find a problem in one of these browsers please report it as we'd like to fix the bug! If you find a bug in another browser we would also like to be aware of it!

Caveats

  • IE 11 - wasm-bindgen by default requires support for WebAssembly, but no version of IE currently supports WebAssembly. You can support IE by compiling wasm files to JS using wasm2js (you can see an example of doing this too). Note that at this time no bundler will do this by default, but we'd love to document plugins which do this if you are aware of one!

  • Edge - the TextEncoder and TextDecoder APIs are not currently available in Edge which wasm-bindgen uses to encode/decode strings between JS and Rust. You can polyfill this with at least one of two strategies:

    1. If using a bundler, you can likely configure the bundler to polyfill these types by default. For example if you're using Webpack you can use the ProvidePlugin interface like so after also adding text-encoding to your package.json

      const webpack = require('webpack');
      
      module.exports = {
          plugins: [
              new webpack.ProvidePlugin({
                TextDecoder: ['text-encoding', 'TextDecoder'],
                TextEncoder: ['text-encoding', 'TextEncoder']
              })
          ]
      
          // ... other configuration options
      };
      
    2. If you're not using a bundler you can also include support manually by adding a <script> tag which defines the TextEncoder and TextDecoder globals. This StackOverflow question has some example usage and MDN has a TextEncoder polyfill implementation to get you started as well.

  • BigInt and u64 - currently the WebAssembly specification for the web forbids the usage of 64-bit integers (Rust types i64 and u64) in exported/imported functions. When using wasm-bindgen, however, u64 is allowed! The reason for this is that it's translated to the BigInt type in JS. The BigInt class, however, is only currently supported in Chrome (as of the time of this writing) and isn't supported in Firefox or Edge, for example.

If you find other incompatibilities please report them to us! We'd love to either keep this list up-to-date or fix the underlying bugs :)

Supported Rust Types and their JavaScript Representations

This section provides an overview of all the types that wasm-bindgen can send and receive across the WebAssembly ABI boundary, and how they translate into JavaScript.

Imported extern Whatever; JavaScript Types

T parameter &T parameter &mut T parameter T return value Option<T> parameter Option<T> return value JavaScript representation
Yes Yes No Yes Yes Yes Instances of the extant Whatever JavaScript class / prototype constructor

Example Rust Usage


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    pub type SomeJsType;
}

#[wasm_bindgen]
pub fn imported_type_by_value(x: SomeJsType) {
    /* ... */
}

#[wasm_bindgen]
pub fn imported_type_by_shared_ref(x: &SomeJsType) {
    /* ... */
}

#[wasm_bindgen]
pub fn return_imported_type() -> SomeJsType {
    unimplemented!()
}

#[wasm_bindgen]
pub fn take_option_imported_type(x: Option<SomeJsType>) {
    /* ... */
}

#[wasm_bindgen]
pub fn return_option_imported_type() -> Option<SomeJsType> {
    unimplemented!()
}

#}

Example JavaScript Usage

import {
  imported_type_by_value,
  imported_type_by_shared_ref,
  return_imported_type,
  take_option_imported_type,
  return_option_imported_type,
} from './guide_supported_types_examples';

imported_type_by_value(new SomeJsType());
imported_type_by_shared_ref(new SomeJsType());

let x = return_imported_type();
console.log(x instanceof SomeJsType); // true

take_option_imported_type(null);
take_option_imported_type(undefined);
take_option_imported_type(new SomeJsType());

let y = return_option_imported_type();
if (y == null) {
  // ...
} else {
  console.log(y instanceof SomeJsType); // true
}

Exported struct Whatever Rust Types

T parameter &T parameter &mut T parameter T return value Option<T> parameter Option<T> return value JavaScript representation
Yes Yes Yes Yes No No Instances of a wasm-bindgen-generated JavaScript class Whatever { ... }

Example Rust Usage


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct ExportedRustType {
    inner: u32,
}

#[wasm_bindgen]
pub fn exported_type_by_value(x: ExportedRustType) {}

#[wasm_bindgen]
pub fn exported_type_by_shared_ref(x: &ExportedRustType) {}

#[wasm_bindgen]
pub fn exported_type_by_exclusive_ref(x: &mut ExportedRustType) {}

#[wasm_bindgen]
pub fn return_exported_type() -> ExportedRustType {
    unimplemented!()
}

#}

Example JavaScript Usage

import {
  ExportedRustType,
  exported_type_by_value,
  exported_type_by_shared_ref,
  exported_type_by_exclusive_ref,
  return_exported_type,
} from './guide_supported_types_examples';

let rustThing = return_exported_type();
console.log(rustThing instanceof ExportedRustType); // true

exported_type_by_value(rustThing);
exported_type_by_shared_ref(rustThing);
exported_type_by_exclusive_ref(rustThing);

JsValue

T parameter &T parameter &mut T parameter T return value Option<T> parameter Option<T> return value JavaScript representation
Yes Yes No Yes No No Any JavaScript value

Example Rust Usage


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn take_js_value_by_value(x: JsValue) {}

#[wasm_bindgen]
pub fn take_js_value_by_shared_ref(x: &JsValue) {}

#[wasm_bindgen]
pub fn return_js_value() -> JsValue {
    JsValue::NULL
}

#}

Example JavaScript Usage

import {
  take_js_value_by_value,
  take_js_value_by_shared_ref,
  return_js_value,
} from './guide_supported_types_examples';

take_js_value_by_value(42);
take_js_value_by_shared_ref('hello');

let v = return_js_value();

Box<[JsValue]>

T parameter &T parameter &mut T parameter T return value Option<T> parameter Option<T> return value JavaScript representation
Yes No No Yes Yes Yes A JavaScript Array object

Example Rust Usage


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn take_boxed_js_value_slice_by_value(x: Box<[JsValue]>) {}

#[wasm_bindgen]
pub fn return_boxed_js_value_slice() -> Box<[JsValue]> {
    vec![JsValue::NULL, JsValue::UNDEFINED].into_boxed_slice()
}

#[wasm_bindgen]
pub fn take_option_boxed_js_value_slice(x: Option<Box<[JsValue]>>) {}

#[wasm_bindgen]
pub fn return_option_boxed_js_value_slice() -> Option<Box<[JsValue]>> {
    None
}

#}

Example JavaScript Usage

import {
  take_boxed_js_value_slice_by_value,
  return_boxed_js_value_slice,
  take_option_boxed_js_value_slice,
  return_option_boxed_js_value_slice,
} from './guide_supported_types_examples';

take_boxed_js_value_slice_by_value([null, true, 2, {}, []]);

let values = return_boxed_js_value_slice();
console.log(values instanceof Array); // true

take_option_boxed_js_value_slice(null);
take_option_boxed_js_value_slice(undefined);
take_option_boxed_js_value_slice([1, 2, 3]);

let maybeValues = return_option_boxed_js_value_slice();
if (maybeValues == null) {
  // ...
} else {
  console.log(maybeValues instanceof Array); // true
}

*const T and *mut T

T parameter &T parameter &mut T parameter T return value Option<T> parameter Option<T> return value JavaScript representation
Yes No No Yes No No A JavaScript number value

Example Rust Usage


# #![allow(unused_variables)]
#fn main() {
use std::ptr;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn take_pointer_by_value(x: *mut u8) {}

#[wasm_bindgen]
pub fn return_pointer() -> *mut u8 {
    ptr::null_mut()
}

#}

Example JavaScript Usage

import {
  take_pointer_by_value,
  return_pointer,
} from './guide_supported_types_examples';
import { memory } from './guide_supported_types_examples_bg';

let ptr = return_pointer();
let buf = new Uint8Array(memory.buffer);
let value = buf[ptr];
console.log(`The byte at the ${ptr} address is ${value}`);

take_pointer_by_value(ptr);

Numbers: u8, i8, u16, i16, u32, i32, u64, i64, isize, usize, f32, and f64

T parameter &T parameter &mut T parameter T return value Option<T> parameter Option<T> return value JavaScript representation
Yes No No Yes Yes Yes A JavaScript number value

Example Rust Usage


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn take_number_by_value(x: u32) {}

#[wasm_bindgen]
pub fn return_number() -> f64 {
    42.0
}

#[wasm_bindgen]
pub fn take_option_number(x: Option<u8>) {}

#[wasm_bindgen]
pub fn return_option_number() -> Option<i16> {
    Some(-300)
}

#}

Example JavaScript Usage

import {
  take_number_by_value,
  return_number,
  take_option_number,
  return_option_number,
} from './guide_supported_types_examples';

take_number_by_value(42);

let x = return_number();
console.log(typeof x); // "number"

take_option_number(null);
take_option_number(undefined);
take_option_number(13);

let y = return_option_number();
if (y == null) {
  // ...
} else {
  console.log(typeof y); // "number"
}

bool

T parameter &T parameter &mut T parameter T return value Option<T> parameter Option<T> return value JavaScript representation
Yes No No Yes Yes Yes A JavaScript boolean value

Example Rust Usage


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn take_bool_by_value(x: bool) {}

#[wasm_bindgen]
pub fn return_bool() -> bool {
    true
}

#[wasm_bindgen]
pub fn take_option_bool(x: Option<bool>) {}

#[wasm_bindgen]
pub fn return_option_bool() -> Option<bool> {
    Some(false)
}

#}

Example JavaScript Usage

import {
  take_char_by_value,
  return_char,
  take_option_bool,
  return_option_bool,
} from './guide_supported_types_examples';

take_bool_by_value(true);

let b = return_bool();
console.log(typeof b); // "boolean"

take_option_bool(null);
take_option_bool(undefined);
take_option_bool(true);

let c = return_option_bool();
if (c == null) {
  // ...
} else {
  console.log(typeof c); // "boolean"
}

char

T parameter &T parameter &mut T parameter T return value Option<T> parameter Option<T> return value JavaScript representation
Yes No No Yes No No A JavaScript string value

Example Rust Usage


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn take_char_by_value(x: char) {}

#[wasm_bindgen]
pub fn return_char() -> char {
    '🚀'
}

#}

Example JavaScript Usage

import {
  take_char_by_value,
  return_char,
} from './guide_supported_types_examples';

take_char_by_value('a');

let c = return_char();
console.log(typeof c); // "string"

str

T parameter &T parameter &mut T parameter T return value Option<T> parameter Option<T> return value JavaScript representation
No Yes No No No No JavaScript string value

Copies the string's contents back and forth between the JavaScript garbage-collected heap and the Wasm linear memory with TextDecoder and TextEncoder. If you don't want to perform this copy, and would rather work with handles to JavaScript string values, use the js_sys::JsString type.

Example Rust Usage


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn take_str_by_shared_ref(x: &str) {}

#}

Example JavaScript Usage

import {
  take_str_by_shared_ref,
} from './guide_supported_types_examples';

take_str_by_shared_ref('hello');

String

T parameter &T parameter &mut T parameter T return value Option<T> parameter Option<T> return value JavaScript representation
Yes No No Yes Yes Yes JavaScript string value

Copies the string's contents back and forth between the JavaScript garbage-collected heap and the Wasm linear memory with TextDecoder and TextEncoder

Example Rust Usage


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn take_string_by_value(x: String) {}

#[wasm_bindgen]
pub fn return_string() -> String {
    "hello".into()
}

#[wasm_bindgen]
pub fn take_option_string(x: Option<String>) {}

#[wasm_bindgen]
pub fn return_option_string() -> Option<String> {
    None
}

#}

Example JavaScript Usage

import {
  take_string_by_value,
  return_string,
  take_option_string,
  return_option_string,
} from './guide_supported_types_examples';

take_string_by_value('hello');

let s = return_string();
console.log(typeof s); // "string"

take_option_string(null);
take_option_string(undefined);
take_option_string('hello');

let t = return_option_string();
if (t == null) {
  // ...
} else {
  console.log(typeof s); // "string"
}

Number Slices: [u8], [i8], [u16], [i16], [u32], [i32], [u64], [i64], [f32], and [f64]

T parameter &T parameter &mut T parameter T return value Option<&T> parameter Option<T> return value JavaScript representation
No Yes Yes No No No A JavaScript TypedArray view of the Wasm memory for the boxed slice of the appropriate type (Int32Array, Uint8Array, etc)

Example Rust Usage


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn take_number_slice_by_shared_ref(x: &[f64]) {}

#[wasm_bindgen]
pub fn take_number_slice_by_exclusive_ref(x: &mut [u8]) {}

#}

Example JavaScript Usage

import {
  take_number_slice_by_value,
  return_number_slice,
  take_option_number_slice,
  return_option_number_slice,
} from './guide_supported_types_examples';

take_number_slice_by_shared_ref(new Float64Array(100));
take_number_slice_by_exclusive_ref(new Uint8Array(100));

Boxed Number Slices: Box<[u8]>, Box<[i8]>, Box<[u16]>, Box<[i16]>, Box<[u32]>, Box<[i32]>, Box<[u64]>, Box<[i64]>, Box<[f32]>, and Box<[f64]>

T parameter &T parameter &mut T parameter T return value Option<T> parameter Option<T> return value JavaScript representation
Yes No No Yes Yes Yes A JavaScript TypedArray of the appropriate type (Int32Array, Uint8Array, etc...)

Note that the contents of the slice are copied into the JavaScript TypedArray from the Wasm linear memory when returning a boxed slice to JavaScript, and vice versa when receiving a JavaScript TypedArray as a boxed slice in Rust.

Example Rust Usage


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn take_boxed_number_slice_by_value(x: Box<[f64]>) {}

#[wasm_bindgen]
pub fn return_boxed_number_slice() -> Box<[u32]> {
    (0..42).collect::<Vec<u32>>().into_boxed_slice()
}

#[wasm_bindgen]
pub fn take_option_boxed_number_slice(x: Option<Box<[u8]>>) {}

#[wasm_bindgen]
pub fn return_option_boxed_number_slice() -> Option<Box<[i32]>> {
    None
}

#}

Example JavaScript Usage

import {
  take_boxed_number_slice_by_value,
  return_boxed_number_slice,
  take_option_boxed_number_slice,
  return_option_boxed_number_slice,
} from './guide_supported_types_examples';

take_boxed_number_slice_by_value(new Uint8Array(100));

let x = return_boxed_number_slice();
console.log(x instanceof Uint32Array); // true

take_option_boxed_number_slice(null);
take_option_boxed_number_slice(undefined);
take_option_boxed_number_slice(new Int16Array(256));

let y = return_option_boxed_number_slice();
if (y == null) {
  // ...
} else {
  console.log(x instanceof Int32Array); // true
}

Result<T, JsValue>

T parameter &T parameter &mut T parameter T return value Option<T> parameter Option<T> return value JavaScript representation
No No No No No Yes Same as T, or an exception

The Result type can be returned from functions exported to JS as well as closures in Rust. Only Result<T, JsValue> is supported where T can be converted to JS. Whenever Ok(val) is encountered it's converted to JS and handed off, and whenever Err(error) is encountered an exception is thrown in JS with error.

You can use Result to enable handling of JS exceptions with ? in Rust, naturally propagating it upwards to the wasm boundary. Furthermore you can also return custom types in Rust so long as they're all convertible to JsValue.

Note that if you import a JS function with Result you need #[wasm_bindgen(catch)] to be annotated on the import (unlike exported functions, which require no extra annotation). This may not be necessary in the future though and it may work "as is"!.

#[wasm_bindgen] Attributes

The #[wasm_bindgen] macro supports a good amount of configuration for controlling precisely how exports are exported, how imports are imported, and what the generated JavaScript glue ends up looking like. This section is an exhaustive reference of the possibilities!

#[wasm_bindgen] on JavaScript Imports

This section enumerates the attributes available for customizing bindings for JavaScript functions and classes imported into Rust within an extern "C" { ... } block.

catch

The catch attribute allows catching a JavaScript exception. This can be attached to any imported function or method, and the function must return a Result where the Err payload is a JsValue:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    // `catch` on a standalone function.
    #[wasm_bindgen(catch)]
    fn foo() -> Result<(), JsValue>;

    // `catch` on a method.
    type Zoidberg;
    #[wasm_bindgen(catch, method)]
    fn woop_woop_woop(this: &Zoidberg) -> Result<u32, JsValue>;
}
#}

If calling the imported function throws an exception, then Err will be returned with the exception that was raised. Otherwise, Ok is returned with the result of the function.

By default wasm-bindgen will take no action when wasm calls a JS function which ends up throwing an exception. The wasm spec right now doesn't support stack unwinding and as a result Rust code will not execute destructors. This can unfortunately cause memory leaks in Rust right now, but as soon as wasm implements catching exceptions we'll be sure to add support as well!

constructor

The constructor attribute is used to indicate that the function being bound should actually translate to calling the new operator in JavaScript. The final argument must be a type that's imported from JavaScript, and it's what will get used in the generated glue:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    type Shoes;

    #[wasm_bindgen(constructor)]
    fn new() -> Shoes;
}
#}

This will attach a new static method to the Shoes type, and in JavaScript when this method is called, it will be equivalent to new Shoes().


# #![allow(unused_variables)]
#fn main() {
// Become a cobbler; construct `new Shoes()`
let shoes = Shoes::new();
#}

extends = Class

The extends attribute can be used to say that an imported type extends (in the JS class hierarchy sense) another type. This will generate AsRef, AsMut, and From impls for converting a type into another given that we statically know the inheritance hierarchy:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    type Foo;

    #[wasm_bindgen(extends = Foo)]
    type Bar;
}

let x: &Bar = ...;
let y: &Foo = x.as_ref(); // zero cost cast
#}

The trait implementations generated for the above block are:


# #![allow(unused_variables)]
#fn main() {
impl From<Bar> for Foo { ... }
impl AsRef<Foo> for Bar { ... }
impl AsMut<Foo> for Bar { ... }
#}

The extends = ... attribute can be specified multiple times for longer inheritance chains, and AsRef and such impls will be generated for each of the types.


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    type Foo;

    #[wasm_bindgen(extends = Foo)]
    type Bar;

    #[wasm_bindgen(extends = Foo, extends = Bar)]
    type Baz;
}

let x: &Baz = ...;
let y1: &Bar = x.as_ref();
let y2: &Foo = x.as_ref();
#}

getter and setter

These two attributes can be combined with method to indicate that this is a getter or setter method. A getter-tagged function by default accesses the JavaScript property with the same name as the getter function. A setter's function name is currently required to start with set_ and the property it accesses is the suffix after set\_.

Consider the following JavaScript class that has a getter and setter for the white_russians property:

class TheDude {
  get white_russians() {
    ...
  }
  set white_russians(val) {
    ...
  }
}

We would import this with the following #[wasm_bindgen] attributes:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    type TheDude;

    #[wasm_bindgen(method, getter)]
    fn white_russians(this: &TheDude) -> u32;

    #[wasm_bindgen(method, setter)]
    fn set_white_russians(this: &TheDude, val: u32);
}
#}

Here we're importing the TheDude type and defining the ability to access each object's white_russians property. The first function here is a getter and will be available in Rust as the_dude.white_russians(), and the latter is the setter which is accessible as the_dude.set_white_russians(2). Note that both functions have a this argument as they're tagged with method.

Finally, you can also pass an argument to the getter and setter properties to configure what property is accessed. When the property is explicitly specified then there is no restriction on the method name. For example the below is equivalent to the above:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    type TheDude;

    #[wasm_bindgen(method, getter = white_russians)]
    fn my_custom_getter_name(this: &TheDude) -> u32;

    #[wasm_bindgen(method, setter = white_russians)]
    fn my_custom_setter_name(this: &TheDude, val: u32);
}
#}

Heads up! getter and setter functions are found on the constructor's prototype chain once at load time, cached, and then the cached accessor is invoked on each access. If you need to dynamically walk the prototype chain on every access, add the structural attribute!

// This is the default function Rust will invoke on `the_dude.white_russians()`:
const white_russians = Object.getOwnPropertyDescriptor(
  TheDude.prototype,
  "white_russians"
).get;

// This is what you get by adding `structural`:
const white_russians = function(the_dude) {
  return the_dude.white_russians;
};

final

The final attribute is the converse of the structural attribute. It configures how wasm-bindgen will generate JS imports to call the imported function. Notably a function imported by final never changes after it was imported, whereas a function imported by default (or with structural) is subject to runtime lookup rules such as walking the prototype chain of an object.

The final attribute is intended to be purely related to performance. It ideally has no user-visible effect, and structural imports (the default) should be able to transparently switch to final eventually.

The eventual performance aspect is that with the host bindings proposal then wasm-bindgen will need to generate far fewer JS functino shims to import than it does today. For example, consider this import today:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    type Foo;
    #[wasm_bindgen(method)]
    fn bar(this: &Foo, argument: &str) -> JsValue;
}
#}

Without the final attribute the generated JS looks like this:

// without `final`
export function __wbg_bar_a81456386e6b526f(arg0, arg1, arg2) {
    let varg1 = getStringFromWasm(arg1, arg2);
    return addHeapObject(getObject(arg0).bar(varg1));
}

We can see here that this JS function shim is required, but it's all relatively self-contained. It does, however, execute the bar method in a duck-type-y fashion in the sense that it never validates getObject(arg0) is of type Foo to actually call the Foo.prototype.bar method.

If we instead, however, write this:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    type Foo;
    #[wasm_bindgen(method, final)] // note the change here
    fn bar(this: &Foo, argument: &str) -> JsValue;
}
#}

it generates this JS glue (roughly):

const __wbg_bar_target = Foo.prototype.bar;

export function __wbg_bar_a81456386e6b526f(arg0, arg1, arg2) {
    let varg1 = getStringFromWasm(arg1, arg2);
    return addHeapObject(__wbg_bar_target.call(getObject(arg0), varg1));
}

The difference here is pretty subtle, but we can see how the function being called is hoisted out of the generated shim and is bound to always be Foo.prototype.bar. This then uses the Function.call method to invoke that function with getObject(arg0) as the receiver.

But wait, there's still a JS function shim here even with final! That's true, and this is simply a fact of future WebAssembly proposals not being implemented yet. The semantics, though, match the future host bindings proposal because the method being called is determined exactly once, and it's located on the prototype chain rather than being resolved at runtime when the function is called.

Interaction with future proposals

If you're curious to see how our JS function shim will be eliminated entirely, let's take a look at the generated bindings. We're starting off with this:

const __wbg_bar_target = Foo.prototype.bar;

export function __wbg_bar_a81456386e6b526f(arg0, arg1, arg2) {
    let varg1 = getStringFromWasm(arg1, arg2);
    return addHeapObject(__wbg_bar_target.call(getObject(arg0), varg1));
}

... and once the reference types proposal is implemented then we won't need some of these pesky functions. That'll transform our generated JS shim to look like:

const __wbg_bar_target = Foo.prototype.bar;

export function __wbg_bar_a81456386e6b526f(arg0, arg1, arg2) {
    let varg1 = getStringFromWasm(arg1, arg2);
    return __wbg_bar_target.call(arg0, varg1);
}

Getting better! Next up we need the host bindings proposal. Note that the proposal is undergoing some changes right now so it's tough to link to reference documentation, but it suffices to say that it'll empower us with at least two different features.

First, host bindings promises to provide the concept of "argument conversions". The arg1 and arg2 values here are actually a pointer and a length to a utf-8 encoded string, and with host bindings we'll be able to annotate that this import should take those two arguments and convert them to a JS string (that is, the host should do this, the WebAssembly engine). Using that feature we can futher trim this down to:

const __wbg_bar_target = Foo.prototype.bar;

export function __wbg_bar_a81456386e6b526f(arg0, varg1) {
    return __wbg_bar_target.call(arg0, varg1);
}

And finally, the second promise of the host bindings proposal is that we can flag a function call to indicate the first argument is the this binding of the function call. Today the this value of all called imported functions is undefined, and this flag (configured with host bindings) will indicate the first argument here is actually the this.

With that in mind we can further transform this to:

export const __wbg_bar_a81456386e6b526f = Foo.prototype.bar;

and voila! We, with reference types and host bindings, now have no JS function shim at all necessary to call the imported function. Additionally future wasm proposals to the ES module system may also mean that don't even need the export const ... here too.

It's also worth pointing out that with all these wasm proposals implemented the default way to import the bar function (aka structural) would generate a JS function shim that looks like:

export function __wbg_bar_a81456386e6b526f(varg1) {
    return this.bar(varg1);
}

where this import is still subject to runtime prototype chain lookups and such.

indexing_getter, indexing_setter, and indexing_deleter

These three attributes indicate that a method is an dynamically intercepted getter, setter, or deleter on the receiver object itself, rather than a direct access of the receiver's properties. It is equivalent calling the Proxy handler for the obj[prop] operation with some dynamic prop variable in JavaScript, rather than a normal static property access like obj.prop on a normal JavaScript Object.

This is useful for binding to Proxys and some builtin DOM types that dynamically intercept property accesses.

  • indexing_getter corresponds to obj[prop] operation in JavaScript. The function annotated must have a this receiver parameter, a single parameter that is used for indexing into the receiver (prop), and a return type.

  • indexing_setter corresponds to the obj[prop] = val operation in JavaScript. The function annotated must have a this receiver parameter, a parameter for indexing into the receiver (prop), and a value parameter (val).

  • indexing_deleter corresponds to delete obj[prop] operation in JavaScript. The function annotated must have a this receiver and a single parameter for indexing into the receiver (prop).

These must always be used in conjunction with the structural and method flags.

For example, consider this JavaScript snippet that uses Proxy:

const foo = new Proxy({}, {
    get(obj, prop) {
        return prop in obj ? obj[prop] : prop.length;
    },
    set(obj, prop, value) {
        obj[prop] = value;
    },
    deleteProperty(obj, prop) {
        delete obj[prop];
    },
});

foo.ten;
// 3

foo.ten = 10;
foo.ten;
// 10

delete foo.ten;
foo.ten;
// 3

To bind that in wasm-bindgen in Rust, we would use the indexing_* attributes on methods:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    type Foo;
    static foo: Foo;

    #[wasm_bindgen(method, structural, indexing_getter)]
    fn get(this: &Foo, prop: &str) -> u32;

    #[wasm_bindgen(method, structural, indexing_setter)]
    fn set(this: &Foo, prop: &str, val: u32);

    #[wasm_bindgen(method, structural, indexing_deleter)]
    fn delete(this: &Foo, prop: &str);
}

assert_eq!(foo.get("ten"), 3);

foo.set("ten", 10);
assert_eq!(foo.get("ten"), 10);

foo.delete("ten");
assert_eq!(foo.get("ten"), 3);
#}

js_class = "Blah"

The js_class attribute can be used in conjunction with the method attribute to bind methods of imported JavaScript classes that have been renamed on the Rust side.


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    // We don't want to import JS strings as `String`, since Rust already has a
    // `String` type in its prelude, so rename it as `JsString`.
    #[wasm_bindgen(js_name = String)]
    type JsString;

    // This is a method on the JavaScript "String" class, so specify that with
    // the `js_class` attribute.
    #[wasm_bindgen(method, js_class = "String", js_name = charAt)]
    fn char_at(this: &JsString, index: u32) -> JsString;
}
#}

js_name = blah

The js_name attribute can be used to bind to a different function in JavaScript than the identifier that's defined in Rust.

Most often, this is used to convert a camel-cased JavaScript identifier into a snake-cased Rust identifier:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_name = jsOftenUsesCamelCase)]
    fn js_often_uses_camel_case() -> u32;
}
#}

Sometimes, it is used to bind to JavaScript identifiers that are not valid Rust identifiers, in which case js_name = "some string" is used instead of js_name = ident:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_name = "$$$")]
    fn cash_money() -> u32;
}
#}

However, you can also use js_name to define multiple signatures for polymorphic JavaScript functions:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn console_log_str(s: &str);

    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn console_log_u32(n: u32);

    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn console_log_many(a: u32, b: &JsValue);
}
#}

All of these functions will call console.log in JavaScript, but each identifier will have only one signature in Rust.

Note that if you use js_name when importing a type you'll also need to use the js_class attribute when defining methods on the type:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_name = String)]
    type JsString;
    #[wasm_bindgen(method, getter, js_class = "String")]
    pub fn length(this: &JsString) -> u32;
}
#}

js_namespace = blah

This attribute indicates that the JavaScript type is accessed through the given namespace. For example, the WebAssembly.Module APIs are all accessed through the WebAssembly namespace. js_namespace can be applied to any import (function or type) and whenever the generated JavaScript attempts to reference a name (like a class or function name) it'll be accessed through this namespace.


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

log("hello, console!");
#}

This is an example of how to bind console.log in Rust. The log function will be available in the Rust module and will be invoked as console.log in JavaScript.

method

The method attribute allows you to describe methods of imported JavaScript objects. It is applied on a function that has this as its first parameter, which is a shared reference to an imported JavaScript type.


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    type Set;

    #[wasm_bindgen(method)]
    fn has(this: &Set, element: &JsValue) -> bool;
}
#}

This generates a has method on Set in Rust, which invokes the Set.prototype.has method in JavaScript.


# #![allow(unused_variables)]
#fn main() {
let set: Set = ...;
let elem: JsValue = ...;
if set.has(&elem) {
    ...
}
#}

module = "blah"

The module attributes configures the module from which items are imported. For example,


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen(module = "wu/tang/clan")]
extern "C" {
    type ThirtySixChambers;
}
#}

generates JavaScript import glue like:

import { ThirtySixChambers } from "wu/tang/clan";

If a module attribute is not present, then the global scope is used instead. For example,


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    fn illmatic() -> u32;
}
#}

generates JavaScript import glue like:

let illmatic = this.illmatic;

static_method_of = Blah

The static_method_of attribute allows one to specify that an imported function is a static method of the given imported JavaScript class. For example, to bind to JavaScript's Date.now() static method, one would use this attribute:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    type Date;

    #[wasm_bindgen(static_method_of = Date)]
    pub fn now() -> f64;
}
#}

The now function becomes a static method of the imported type in the Rust bindings as well:


# #![allow(unused_variables)]
#fn main() {
let instant = Date::now();
#}

This is similar to the js_namespace attribute, but the usage from within Rust is different since the method also becomes a static method of the imported type.

structural

Note: As of RFC 5 this attribute is the default for all imported functions. This attribute is largely ignored today and is only retained for backwards compatibility and learning purposes.

The inverse of this attribute, the final attribute is more functionally interesting than structural (as structural is simply the default)

The structural flag can be added to method annotations, indicating that the method being accessed (or property with getters/setters) should be accessed in a structural, duck-type-y fashion. Rather than walking the constructor's prototype chain once at load time and caching the property result, the prototype chain is dynamically walked on every access.


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    type Duck;

    #[wasm_bindgen(method, structural)]
    fn quack(this: &Duck);

    #[wasm_bindgen(method, getter, structural)]
    fn is_swimming(this: &Duck) -> bool;
}
#}

The constructor for the type here, Duck, is not required to exist in JavaScript (it's not referenced). Instead wasm-bindgen will generate shims that will access the passed in JavaScript value's quack method or its is_swimming property.

// Without `structural`, get the method directly off the prototype at load time:
const Duck_prototype_quack = Duck.prototype.quack;
function quack(duck) {
  Duck_prototype_quack.call(duck);
}

// With `structural`, walk the prototype chain on every access:
function quack(duck) {
  duck.quack();
}

Variadic Parameters

In javascript, both the types of function arguments, and the number of function arguments are dynamic. For example

function sum(...rest) {
    let i;
    // the old way
    let old_way = 0;
    for (i=0; i<arguments.length; i++) {
        old_way += arguments[i];
    }
    // the new way
    let new_way = 0;
    for (i=0; i<rest.length; i++) {
        new_way += rest[i];
    }
    // both give the same answer
    assert(old_way === new_way);
    return new_way;
}

This function doesn't translate directly into rust, since we don't currently support variadic arguments on the wasm target. To bind to it, we use a slice as the last argument, and annotate the function as variadic:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(variadic)]
    fn sum(args: &[i32]) -> i32;
}
#}

when we call this function, the last argument will be expanded as the javascript expects.

Vendor-prefixed APIs

On the web new APIs often have vendor prefixes while they're in an experimental state. For example the AudioContext API is known as webkitAudioContext in Safari at the time of this writing. The vendor_prefix attribute indicates these alternative names, which are used if the normal name isn't defined.

For example to use AudioContext you might do:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(vendor_prefix = webkit)]
    type AudioContext;

    // methods on `AudioContext` ...
}
#}

Whenever AudioContext is used it'll use AudioContext if the global namespace defines it or alternatively it'll fall back to webkitAudioContext.

Note that vendor_prefix cannot be used with module = "..." or js_namespace = ..., so it's basically limited to web-platform APIs today.

#[wasm_bindgen] on Rust Exports

This section enumerates the attributes available for customizing bindings for Rust functions and structs exported to JavaScript.

constructor

When attached to a Rust "constructor" it will make the generated JavaScript bindings callable as new Foo().

For example, consider this exported Rust type and constructor annotation:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
pub struct Foo {
    contents: u32,
}

#[wasm_bindgen]
impl Foo {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Foo {
        Foo { contents: 0 }
    }

    pub fn get_contents(&self) -> u32 {
        self.contents
    }
}
#}

This can be used in JavaScript as:

import { Foo } from './my_module';

const f = new Foo();
console.log(f.get_contents());

js_name = Blah

The js_name attribute can be used to export a different name in JS than what something is named in Rust. It can be applied to both exported Rust functions and types.

For example, this is often used to convert between Rust's snake-cased identifiers into JavaScript's camel-cased identifiers:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen(js_name = doTheThing)]
pub fn do_the_thing() -> u32 {
    42
}
#}

This can be used in JavaScript as:

import { doTheThing } from './my_module';

const x = doTheThing();
console.log(x);

Like imports, js_name can also be used to rename types exported to JS:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen(js_name = Foo)]
pub struct JsFoo {
    // ..
}
#}

to be accessed like:

import { Foo } from './my_module';

// ...

Note that attaching methods to the JS class Foo should be done via the js_class attribute:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen(js_name = Foo)]
pub struct JsFoo { /* ... */ }

#[wasm_bindgen(js_class = Foo)]
impl JsFoo {
    // ...
}
#}

readonly

When attached to a pub struct field this indicates that it's read-only from JavaScript, and a setter will not be generated and exported to JavaScript.


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
pub fn make_foo() -> Foo {
    Foo {
        first: 10,
        second: 20,
    }
}

#[wasm_bindgen]
pub struct Foo {
    pub first: u32,

    #[wasm_bindgen(readonly)]
    pub second: u32,
}
#}

Here the first field will be both readable and writable from JS, but the second field will be a readonly field in JS where the setter isn't implemented and attempting to set it will throw an exception.

import { make_foo } from "./my_module";

const foo = make_foo();

// Can both get and set `first`.
foo.first = 99;
console.log(foo.first);

// Can only get `second`.
console.log(foo.second);

start

When attached to a pub function this attribute will configure the start section of the wasm executable to be emitted, executing the tagged function as soon as the wasm module is instantiated.

#[wasm_bindgen(start)]
pub fn main() {
    // executed automatically ...
}

The start section of the wasm executable will be configured to execute the main function here as soon as it can. Note that due to various practical limitations today the start section of the executable may not literally point to main, but the main function here should be started up automatically when the wasm module is loaded.

There's a few caveats to be aware of when using the start attribute:

  • The start function must take no arguments and must either return () or Result<(), JsValue>
  • Only one start function can be placed into a module, including its dependencies. If more than one is specified then wasm-bindgen will fail when the CLI is run. It's recommended that only applications use this attribute.
  • The start function will not be executed when testing.
  • If you're experimenting with WebAssembly threads, the start function is executed once per thread, not once globally!
  • Note that the start function is relatively new, so if you find any bugs with it, please feel free to report an issue!

typescript_custom_section

When added to a const &'static str, it will append the contents of the string to the .d.ts file exported by wasm-bindgen-cli (when the --typescript flag is enabled).


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen(typescript_custom_section)]
const TS_APPEND_CONTENT: &'static str = r#"

export type Coords = { "latitude": number, "longitude": number, }; 

"#;
#}

The primary target for this feature is for code generation. For example, you can author a macro that allows you to export a TypeScript definition alongside the definition of a struct or Rust type.


# #![allow(unused_variables)]
#fn main() {
#[derive(MyTypescriptExport)]
struct Coords {
    latitude: u32,
    longitude: u32,
}
#}

The proc_derive_macro "MyTypescriptExport" can export its own #[wasm_bindgen(typescript_custom_section)] section, which would then be picked up by wasm-bindgen-cli. This would be equivalent to the contents of the TS_APPEND_CONTENT string in the first example.

This feature allows plain data objects to be typechecked in Rust and in TypeScript by outputing a type definition generated at compile time.

The web-sys Crate

The web-sys crate provides raw wasm-bindgen imports for all of the Web's APIs. This includes:

  • window.fetch
  • Node.prototype.appendChild
  • WebGL
  • WebAudio
  • and many more!

It's sort of like the libc crate, but for the Web.

It does not include the JavaScript APIs that are guaranteed to exist in all standards-compliant ECMAScript environments, such as Array, Date, and eval. Bindings for these APIs can be found in the js-sys crate.

API Documentation

Read the web-sys API documentation here!

Using web-sys

Add web-sys as a dependency to your Cargo.toml

[dependencies]
wasm-bindgen = "0.2"

[dependencies.web-sys]
version = "0.3"
features = [
]

Enable the cargo features for the APIs you're using

To keep build times super speedy, web-sys gates each Web interface behind a cargo feature. Find the type or method you want to use in the API documentation; it will list the features that must be enabled to access that API.

For example, if we're looking for the window.resizeTo function, we would search for resizeTo in the API documentation. We would find the web_sys::Window::resize_to function, which requires the Window feature. To get access to that function, we enable the Window feature in Cargo.toml:

[dependencies.web-sys]
version = "0.3"
features = [
  "Window"
]

Call the method!


# #![allow(unused_variables)]
#fn main() {
extern crate web_sys;
extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;
use web_sys::Window;

#[wasm_bindgen]
pub fn make_the_window_small() {
    // Resize the window to 500px by 500px.
    let window = web_sys::window().unwrap();
    window.resize_to(500, 500)
        .expect("could not resize the window");
}
#}

Cargo Features in web-sys

To keep web-sys building as fast as possible, there is a cargo feature for every type defined in web-sys. To access that type, you must enable its feature. To access a method, you must enable the feature for its self type and the features for each of its argument types. In the API documentation, every method lists the features that are required to enable it.

For example, the WebGlRenderingContext::compile_shader function requires these features:

  • WebGlRenderingContext, because that is the method's self type
  • WebGlShader, because it takes an argument of that type

Function Overloads

Many Web APIs are overloaded to take different types of arguments or to skip arguments completely. web-sys contains multiple bindings for these functions that each specialize to a particular overload and set of argument types.

For example, the fetch API can be given a URL string, or a Request object, and it might also optionally be given a RequestInit options object. Therefore, we end up with these web-sys functions that all bind to the window.fetch function:

Note that different overloads can use different interfaces, and therefore can require different sets of cargo features to be enabled.

Type Translations in web-sys

Most of the types specified in WebIDL (the interface definition language for all Web APIs) have relatively straightforward translations into web-sys, but it's worth calling out a few in particular:

  • BufferSource and ArrayBufferView - these two types show up in a number of APIs that generally deal with a buffer of bytes. We bind them in web-sys with two different types, js_sys::Object and &mut [u8]. Using js_sys::Object allows passing in arbitrary JS values which represent a view of bytes (like any typed array object), and &mut [u8] allows using a raw slice in Rust. Unfortunately we must pessimistically assume that JS will modify all slices as we don't currently have information of whether they're modified or not.

  • Callbacks are all represented as js_sys::Function. This means that all callbacks going through web-sys are a raw JS value. You can work with this by either juggling actual js_sys::Function instances or you can create a Closure<FnMut(...)>, extract the underlying JsValue with as_ref, and then use JsCast::unchecked_ref to convert it to a js_sys::Function.

Inheritance in web-sys

Inheritance between JS classes is the bread and butter of how the DOM works on the web, and as a result it's quite important for web-sys to provide access to this inheritance hierarchy as well! There are few ways you can access the inheritance hierarchy when using web-sys.

Accessing parent classes using Deref

Like smart pointers in Rust, all types in web_sys implement Deref to their parent JS class. This means, for example, if you have a web_sys::Element you can create a web_sys::Node from that implicitly:


# #![allow(unused_variables)]
#fn main() {
let element: &Element = ...;

element.append_child(..); // call a method on `Node`

method_expecting_a_node(&element); // coerce to `&Node` implicitly

let node: &Node = &element; // explicitly coerce to `&Node`
#}

Using Deref allows ergonomic transitioning up the inheritance hierarchy to the parent class and beyond, giving you access to all the methods using the . operator.

Accessing parent classes using AsRef

In addition to Deref, the AsRef trait is implemented for all types in web_sys for all types in the inheritance hierarchy. For example for the HtmlAnchorElement type you'll find:


# #![allow(unused_variables)]
#fn main() {
impl AsRef<HtmlElement> for HtmlAnchorElement
impl AsRef<Element> for HtmlAnchorElement
impl AsRef<Node> for HtmlAnchorElement
impl AsRef<EventTarget> for HtmlAnchorElement
impl AsRef<Object> for HtmlAnchorElement
impl AsRef<JsValue> for HtmlAnchorElement
#}

You can use .as_ref() to explicitly get a reference to any parent class from from a type in web_sys. Note that because of the number of AsRef implementations you'll likely need to have type inference guidance as well.

Accessing child clases using JsCast

Finally the wasm_bindgen::JsCast trait can be used to implement all manner of casts between types. It supports static unchecked casts between types as well as dynamic runtime-checked casts (using instanceof) between types.

More documentation about this can be found on the trait itself

Testing on wasm32-unknown-unknown with wasm-bindgen-test

The wasm-bindgen-test crate is an experimental test harness for Rust programs compiled to wasm using wasm-bindgen and the wasm32-unknown-unknown target.

Goals

  • Write tests for wasm as similar as possible to how you normally would write #[test]-style unit tests for native targets.

  • Run the tests with the usual cargo test command but with an explicit wasm target:

    cargo test --target wasm32-unknown-unknown
    

Using wasm-bindgen-test

Add wasm-bindgen-test to Your Cargo.toml's [dev-dependencies]

Make sure to replace "X.Y.Z" with the same version of wasm-bindgen that you have in the [dependencies] section!

[dev-dependencies]
wasm-bindgen-test = "X.Y.Z"

Write Some Tests

Create a $MY_CRATE/tests/wasm.rs file:


# #![allow(unused_variables)]
#fn main() {
extern crate wasm_bindgen_test;
use wasm_bindgen_test::*;

#[wasm_bindgen_test]
fn pass() {
    assert_eq!(1, 1);
}

#[wasm_bindgen_test]
fn fail() {
    assert_eq!(1, 2);
}
#}

Writing tests is the same as normal Rust #[test]s, except we are using the #[wasm_bindgen_test] attribute.

One other difference is that the tests must be in the root of the crate, or within a pub mod. Putting them inside a private module will not work.

Execute Your Tests

Run the tests with wasm-pack test. By default, the tests are generated to target Node.js, but you can configure tests to run inside headless browsers as well.

$ wasm-pack test --node
    Finished dev [unoptimized + debuginfo] target(s) in 0.11s
     Running /home/.../target/wasm32-unknown-unknown/debug/deps/wasm-4a309ffe6ad80503.wasm
running 2 tests

test wasm::pass ... ok
test wasm::fail ... FAILED

failures:

---- wasm::fail output ----
    error output:
        panicked at 'assertion failed: `(left == right)`
          left: `1`,
         right: `2`', crates/test/tests/wasm.rs:14:5

    JS exception that was thrown:
        RuntimeError: unreachable
            at __rust_start_panic (wasm-function[1362]:33)
            at rust_panic (wasm-function[1357]:30)
            at std::panicking::rust_panic_with_hook::h56e5e464b0e7fc22 (wasm-function[1352]:444)
            at std::panicking::continue_panic_fmt::had70ba48785b9a8f (wasm-function[1350]:122)
            at std::panicking::begin_panic_fmt::h991e7d1ca9bf9c0c (wasm-function[1351]:95)
            at wasm::fail::ha4c23c69dfa0eea9 (wasm-function[88]:477)
            at core::ops::function::FnOnce::call_once::h633718dad359559a (wasm-function[21]:22)
            at wasm_bindgen_test::__rt::Context::execute::h2f669104986475eb (wasm-function[13]:291)
            at __wbg_test_fail_1 (wasm-function[87]:57)
            at module.exports.__wbg_apply_2ba774592c5223a7 (/home/alex/code/wasm-bindgen/target/wasm32-unknown-unknown/wbg-tmp/wasm-4a309ffe6ad80503.js:61:66)


failures:

    wasm::fail

test result: FAILED. 1 passed; 1 failed; 0 ignored

error: test failed, to rerun pass '--test wasm'

That's it!


Appendix: Using wasm-bindgen-test without wasm-pack

⚠️ The recommended way to use wasm-bindgen-test is with wasm-pack, since it will handle installing the test runner, installing a WebDriver client for your browser, and informing cargo how to use the custom test runner. However, you can also manage those tasks yourself, if you wish.

In addition to the steps above, you must also do the following.

Install the Test Runner

The test runner comes along with the main wasm-bindgen CLI tool. Make sure to replace "X.Y.Z" with the same version of wasm-bindgen that you already have in Cargo.toml!

cargo install wasm-bindgen-cli --vers "X.Y.Z"

Configure .cargo/config to use the Test Runner

Add this to $MY_CRATE/.cargo/config:

[target.wasm32-unknown-unknown]
runner = 'wasm-bindgen-test-runner'

Run the Tests

Run the tests by passing --target wasm32-unknown-unknown to cargo test:

cargo test --target wasm32-unknown-unknown

Writing Asynchronous Tests

Not all tests can execute immediately and some may need to do "blocking" work like fetching resources and/or other bits and pieces. To accommodate this asynchronous tests are also supported through the futures and wasm-bindgen-futures crates.

To write an asynchronous test:

  1. Change #[wasm_bindgen_test] into #[wasm_bindgen_test(async)]

  2. Change the return type of the test function to impl Future<Item = (), Error = JsValue>

The test will pass if the future resolves without panicking or returning an error, and otherwise the test will fail.

Example


# #![allow(unused_variables)]
#fn main() {
extern crate futures;
extern crate js_sys;
extern crate wasm_bindgen_futures;

use futures::Future;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;

#[wasm_bindgen_test(async)]
fn my_async_test() -> impl Future<Item = (), Error = JsValue> {
    // Create a promise that is ready on the next tick of the micro task queue.
    let promise = js_sys::Promise::resolve(&JsValue::from(42));

    // Convert that promise into a future and make the test wait on it.
    JsFuture::from(promise)
        .map(|x| {
            assert_eq!(x, 42);
        })
        .map_err(|_| unreachable!())
}
#}

Testing in Headless Browsers

Configure Your Test Crate

Add this to the root of your test crate, e.g. $MY_CRATE/tests/web.rs:


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen_test::wasm_bindgen_test_configure;

wasm_bindgen_test_configure!(run_in_browser);
#}

Note that although a particular test crate must target either headless browsers or Node.js, you can have test suites for both Node.js and browsers for your project by using multiple test crates. For example:

$MY_CRATE/
`-- tests
    |-- node.rs    # The tests in this suite use the default Node.js.
    `-- web.rs     # The tests in this suite are configured for browsers.

Configuring Which Browser is Used

To control which browser is used for headless testing, use the appropriate flag with wasm-pack test:

  • wasm-pack test --chrome — Run the tests in Chrome. This machine must have Chrome installed.

  • wasm-pack test --firefox — Run the tests in Firefox. This machine must have Firefox installed.

  • wasm-pack test --safari — Run the tests in Safari. This machine must have Safari installed.

If multiple browser flags are passed, the tests will be run under each browser.

Running the Tests in the Headless Browser

Once the tests are configured to run in a headless browser, just run wasm-pack test with the appropriate browser flags and --headless:

wasm-pack test --headless --chrome --firefox --safari

Debugging Headless Browser Tests

Omitting the --headless flag will disable headless mode, and allow you to debug failing tests in your browser's devtools.


Appendix: Testing in headless browsers without wasm-pack

⚠️ The recommended way to use wasm-bindgen-test is with wasm-pack, since it will handle installing the test runner, installing a WebDriver client for your browser, and informing cargo how to use the custom test runner. However, you can also manage those tasks yourself, if you wish.

Configuring Which Browser is Used

If one of the following environment variables is set, then the corresponding WebDriver and browser will be used. If none of these environment variables are set, then the $PATH is searched for a suitable WebDriver implementation.

GECKODRIVER=path/to/geckodriver

Use Firefox for headless browser testing, and geckodriver as its WebDriver.

The firefox binary must be on your $PATH.

Get geckodriver here

CHROMEDRIVER=path/to/chromedriver

Use Chrome for headless browser testing, and chromedriver as its WebDriver.

The chrome binary must be on your $PATH.

Get chromedriver here

SAFARIDRIVER=path/to/safaridriver

Use Safari for headless browser testing, and safaridriver as its WebDriver.

This is installed by default on Mac OS. It should be able to find your Safari installation by default.

Running the Tests in the Headless Browser

Once the tests are configured to run in a headless browser and the appropriate environment variables are set, executing the tests for headless browsers is the same as executing them for Node.js:

cargo test --target wasm32-unknown-unknown

Debugging Headless Browser Tests

Set the NO_HEADLESS=1 environment variable and the browser tests will not run headless. Instead, the tests will start a local server that you can visit in your Web browser of choices, and headless testing should not be used. You can then use your browser's devtools to debug.

Setting Up Continuous Integration with wasm-bindgen-test

This page contains example configurations for running wasm-bindgen-test-based tests in various CI services.

Is your favorite CI service missing? Send us a pull request!

Travis CI

language: rust
rust: nightly

addons:
  firefox: latest
  chrome: stable

install:
  - rustup target add wasm32-unknown-unknown
  # Downloads a `wasm-bindgen` release binary from https://github.com/rustwasm/wasm-bindgen/releases.
  # Alternatively, use `wasm-pack` to manage `wasm-bindgen` binaries for you
  - curl -OL https://github.com/rustwasm/wasm-bindgen/releases/download/0.2.21/wasm-bindgen-0.2.21-x86_64-unknown-linux-musl.tar.gz
  - tar xf wasm-bindgen-0.2.21-x86_64-unknown-linux-musl.tar.gz
  - chmod +x wasm-bindgen-0.2.21-x86_64-unknown-linux-musl/wasm-bindgen
  # Moves the binaries to a directory that is in your PATH
  - mv wasm-bindgen-0.2.19-x86_64-unknown-linux-musl/wasm-bindgen* ~/.cargo/bin 
  # Install node.js with nvm.
  - curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
  - source ~/.nvm/nvm.sh
  - nvm install v10.5
  # Install chromedriver.
  - curl --retry 5 -LO https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip
  - unzip chromedriver_linux64.zip
  # Install geckodriver.
  - curl --retry 5 -LO https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-linux64.tar.gz
  - tar xf geckodriver-v0.21.0-linux64.tar.gz

script:
  # Test in Chrome.
  - CHROMEDRIVER=$(pwd)/chromedriver cargo test --target wasm32-unknown-unknown
  # Test in Firefox.
  - GECKODRIVER=$(pwd)/geckodriver cargo test --target wasm32-unknown-unknown

AppVeyor

install:
  - ps: Install-Product node 10
  - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
  - rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
  - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
  - rustc -V
  - cargo -V
  - rustup target add wasm32-unknown-unknown
  - cargo install wasm-bindgen-cli

build: false

test_script:
  # Test in Chrome. chromedriver is installed by default in appveyor.
  - set CHROMEDRIVER=C:\Tools\WebDriver\chromedriver.exe
  - cargo test --target wasm32-unknown-unknown
  - set CHROMEDRIVER=
  # Test in Firefox. geckodriver is also installed by default.
  - set GECKODRIVER=C:\Tools\WebDriver\geckodriver.exe
  - cargo test --target wasm32-unknown-unknown

Contributing to wasm-bindgen

This section contains instructions on how to get this project up and running for development.

Prerequisites

  1. Rust Nightly. Install Rust. Once Rust is installed, run

    rustup default nightly
    rustup target add wasm32-unknown-unknown
    
  1. The tests for this project use Node. Make sure you have node >= 10 installed, as that is when WebAssembly support was introduced. Install Node.

Running wasm-bindgen's Tests

Wasm Tests on Node and Headless Browsers

These are the largest test suites, and most common to run in day to day wasm-bindgen development. These tests are compiled to Wasm and then run in Node.js or a headless browser via the WebDriver protocol.

cargo test --target wasm32-unknown-unknown

See the wasm-bindgen-test crate's README.md for details and configuring which headless browser is used.

Sanity Tests for wasm-bindgen on the Native Host Target

This small test suite just verifies that exported wasm-bindgen methods can still be used on the native host's target.

cargo test

The Web IDL Frontend's Tests

cargo test -p webidl-tests --target wasm32-unknown-unknown

The Macro UI Tests

These tests assert that we have reasonable error messages that point to the right source spans when the #[wasm_bindgen] proc-macro is misused.

cargo test -p ui-tests

The js-sys Tests

See the js-sys testing page.

The web-sys Tests

See the web-sys testing page.

Design of wasm-bindgen

This section is intended to be a deep-dive into how wasm-bindgen internally works today, specifically for Rust. If you're reading this far in the future it may no longer be up to date, but feel free to open an issue and we can try to answer questions and/or update this!

Foundation: ES Modules

The first thing to know about wasm-bindgen is that it's fundamentally built on the idea of ES Modules. In other words this tool takes an opinionated stance that wasm files should be viewed as ES modules. This means that you can import from a wasm file, use its export-ed functionality, etc, from normal JS files.

Now unfortunately at the time of this writing the interface of wasm interop isn't very rich. Wasm modules can only call functions or export functions that deal exclusively with i32, i64, f32, and f64. Bummer!

That's where this project comes in. The goal of wasm-bindgen is to enhance the "ABI" of wasm modules with richer types like classes, JS objects, Rust structs, strings, etc. Keep in mind, though, that everything is based on ES Modules! This means that the compiler is actually producing a "broken" wasm file of sorts. The wasm file emitted by rustc, for example, does not have the interface we would like to have. Instead it requires the wasm-bindgen tool to postprocess the file, generating a foo.js and foo_bg.wasm file. The foo.js file is the desired interface expressed in JS (classes, types, strings, etc) and the foo_bg.wasm module is simply used as an implementation detail (it was lightly modified from the original foo.wasm file).

As more features are stabilized in WebAssembly over time (like host bindings) the JS file is expected to get smaller and smaller. It's unlikely to ever disappear, but wasm-bindgen is designed to follow the WebAssembly spec and proposals closely to optimize JS/Rust as much as possible.

Foundation #2: Unintrusive in Rust

On the more Rust-y side of things the wasm-bindgen crate is designed to ideally have as minimal impact on a Rust crate as possible. Ideally a few #[wasm_bindgen] attributes are annotated in key locations and otherwise you're off to the races. The attribute strives to both not invent new syntax and work with existing idioms today.

For example a library might exposed a function in normal Rust that looks like:


# #![allow(unused_variables)]
#fn main() {
pub fn greet(name: &str) -> String {
    // ...
}
#}

And with #[wasm_bindgen] all you need to do in exporting it to JS is:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    // ...
}
#}

Additionally the design here with minimal intervention in Rust should allow us to easily take advantage of the upcoming host bindings proposal. Ideally you'd simply upgrade wasm-bindgen-the-crate as well as your toolchain and you're immediately getting raw access to host bindings! (this is still a bit of a ways off though...)

Polyfill for "JS objects in wasm"

One of the main goals of wasm-bindgen is to allow working with and passing around JS objects in wasm, but that's not allowed today! While indeed true, that's where the polyfill comes in.

The question here is how we shoehorn JS objects into a u32 for wasm to use. The current strategy for this approach is to maintain a module-local variable in the generated foo.js file: a heap.

Temporary JS objects on the "stack"

The first slots in the heap in foo.js are considered a stack. This stack, like typical program execution stacks, grows down. JS objects are pushed on the bottom of the stack, and their index in the stack is the identifier that's passed to wasm. A stack pointer is maintained to figure out where the next item is pushed.

JS objects are then only removed from the bottom of the stack as well. Removal is simply storing null then incrementing a counter. Because of the "stack-y" nature of this sceheme it only works for when wasm doesn't hold onto a JS object (aka it only gets a "reference" in Rust parlance).

Let's take a look at an example.


# #![allow(unused_variables)]
#fn main() {
// foo.rs
#[wasm_bindgen]
pub fn foo(a: &JsValue) {
    // ...
}
#}

Here we're using the special JsValue type from the wasm-bindgen library itself. Our exported function, foo, takes a reference to an object. This notably means that it can't persist the object past the lifetime of this function call.

Now what we actually want to generate is a JS module that looks like (in Typescript parlance)

// foo.d.ts
export function foo(a: any);

and what we actually generate looks something like:

// foo.js
import * as wasm from './foo_bg';

const heap = new Array(32);
heap.push(undefined, null, true, false);
let stack_pointer = 32;

function addBorrowedObject(obj) {
  stack_pointer -= 1;
  heap[stack_pointer] = obj;
  return stack_pointer;
}

export function foo(arg0) {
  const idx0 = addBorrowedObject(arg0);
  try {
    wasm.foo(idx0);
  } finally {
    heap[stack_pointer++] = undefined;
  }
}

Here we can see a few notable points of action:

  • The wasm file was renamed to foo_bg.wasm, and we can see how the JS module generated here is importing from the wasm file.
  • Next we can see our heap module variable which is to store all JS values reference-able from wasm.
  • Our exported function foo, takes an arbitrary argument, arg0, which is converted to an index with the addBorrowedObject object function. The index is then passed to wasm so wasm can operate with it.
  • Finally, we have a finally which frees the stack slot as it's no longer used, popping the value that was pushed at the start of the function.

It's also helpful to dig into the Rust side of things to see what's going on there! Let's take a look at the code that #[wasm_bindgen] generates in Rust:


# #![allow(unused_variables)]
#fn main() {
// what the user wrote
pub fn foo(a: &JsValue) {
    // ...
}

#[export_name = "foo"]
pub extern "C" fn __wasm_bindgen_generated_foo(arg0: u32) {
    let arg0 = unsafe {
        ManuallyDrop::new(JsValue::__from_idx(arg0))
    };
    let arg0 = &*arg0;
    foo(arg0);
}
#}

And as with the JS, the notable points here are:

  • The original function, foo, is unmodified in the output
  • A generated function here (with a unique name) is the one that's actually exported from the wasm module
  • Our generated function takes an integer argument (our index) and then wraps it in a JsValue. There's some trickery here that's not worth going into just yet, but we'll see in a bit what's happening under the hood.

Long-lived JS objects

The above strategy is useful when JS objects are only temporarily used in Rust, for example only during one function call. Sometimes, though, objects may have a dynamic lifetime or otherwise need to be stored on Rust's heap. To cope with this there's a second half of management of JS objects, naturally corresponding to the other side of the JS heap array.

JS Objects passed to wasm that are not references are assumed to have a dynamic lifetime inside of the wasm module. As a result the strict push/pop of the stack won't work and we need more permanent storage for the JS objects. To cope with this we build our own "slab allocator" of sorts.

A picture (or code) is worth a thousand words so let's show what happens with an example.


# #![allow(unused_variables)]
#fn main() {
// foo.rs
#[wasm_bindgen]
pub fn foo(a: JsValue) {
    // ...
}
#}

Note that the & is missing in front of the JsValue we had before, and in Rust parlance this means it's taking ownership of the JS value. The exported ES module interface is the same as before, but the ownership mechanics are slightly different. Let's see the generated JS's slab in action:

import * as wasm from './foo_bg'; // imports from wasm file

const heap = new Array(32);
heap.push(undefined, null, true, false);
let heap_next = 36;

function addHeapObject(obj) {
  if (heap_next === heap.length)
    heap.push(heap.length + 1);
  const idx = heap_next;
  heap_next = heap[idx];
  heap[idx] = obj;
  return idx;
}

export function foo(arg0) {
  const idx0 = addHeapObject(arg0);
  wasm.foo(idx0);
}

export function __wbindgen_object_drop_ref(idx) {
  heap[idx ] = heap_next;
  heap_next = idx;
}

Unlike before we're now calling addHeapObject on the argument to foo rather than addBorrowedObject. This function will use heap and heap_next as a slab allocator to acquire a slot to store the object, placing a structure there once it's found. Note that this is going on the right-half of the array, unlike the stack which resides on the left half. This discipline mirrors the stack/heap in normal programs, roughly.

Another curious aspect of this generated module is the __wbindgen_object_drop_ref function. This is one that's actually imported from wasm rather than used in this module! This function is used to signal the end of the lifetime of a JsValue in Rust, or in other words when it goes out of scope. Otherwise though this function is largely just a general "slab free" implementation.

And finally, let's take a look at the Rust generated again too:


# #![allow(unused_variables)]
#fn main() {
// what the user wrote
pub fn foo(a: JsValue) {
    // ...
}

#[export_name = "foo"]
pub extern "C" fn __wasm_bindgen_generated_foo(arg0: u32) {
    let arg0 = unsafe {
        JsValue::__from_idx(arg0)
    };
    foo(arg0);
}
#}

Ah that looks much more familiar! Not much interesting is happening here, so let's move on to...

Anatomy of JsValue

Currently the JsValue struct is actually quite simple in Rust, it's:


# #![allow(unused_variables)]
#fn main() {
pub struct JsValue {
    idx: u32,
}

// "private" constructors

impl Drop for JsValue {
    fn drop(&mut self) {
        unsafe {
            __wbindgen_object_drop_ref(self.idx);
        }
    }
}
#}

Or in other words it's a newtype wrapper around a u32, the index that we're passed from wasm. The destructor here is where the __wbindgen_object_drop_ref function is called to relinquish our reference count of the JS object, freeing up our slot in the slab that we saw above.

If you'll recall as well, when we took &JsValue above we generated a wrapper of ManuallyDrop around the local binding, and that's because we wanted to avoid invoking this destructor when the object comes from the stack.

Working with heap in reality

The above explanations are pretty close to what happens today, but in reality there's a few differences especially around handling constant values like undefined, null, etc. Be sure to check out the actual generated JS and the generation code for the full details!

Exporting a function to JS

Alright now that we've got a good grasp on JS objects and how they're working, let's take a look at another feature of wasm-bindgen: exporting functionality with types that are richer than just numbers.

The basic idea around exporting functionality with more flavorful types is that the wasm exports won't actually be called directly. Instead the generated foo.js module will have shims for all exported functions in the wasm module.

The most interesting conversion here happens with strings so let's take a look at that.


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
pub fn greet(a: &str) -> String {
    format!("Hello, {}!", a)
}
#}

Here we'd like to define an ES module that looks like

// foo.d.ts
export function greet(a: string): string;

To see what's going on, let's take a look at the generated shim

import * as wasm from './foo_bg';

function passStringToWasm(arg) {
  const buf = new TextEncoder('utf-8').encode(arg);
  const len = buf.length;
  const ptr = wasm.__wbindgen_malloc(len);
  let array = new Uint8Array(wasm.memory.buffer);
  array.set(buf, ptr);
  return [ptr, len];
}

function getStringFromWasm(ptr, len) {
  const mem = new Uint8Array(wasm.memory.buffer);
  const slice = mem.slice(ptr, ptr + len);
  const ret = new TextDecoder('utf-8').decode(slice);
  return ret;
}

export function greet(arg0) {
  const [ptr0, len0] = passStringToWasm(arg0);
  try {
    const ret = wasm.greet(ptr0, len0);
    const ptr = wasm.__wbindgen_boxed_str_ptr(ret);
    const len = wasm.__wbindgen_boxed_str_len(ret);
    const realRet = getStringFromWasm(ptr, len);
    wasm.__wbindgen_boxed_str_free(ret);
    return realRet;
  } finally {
    wasm.__wbindgen_free(ptr0, len0);
  }
}

Phew, that's quite a lot! We can sort of see though if we look closely what's happening:

  • Strings are passed to wasm via two arguments, a pointer and a length. Right now we have to copy the string onto the wasm heap which means we'll be using TextEncoder to actually do the encoding. Once this is done we use an internal function in wasm-bindgen to allocate space for the string to go, and then we'll pass that ptr/length to wasm later on.

  • Returning strings from wasm is a little tricky as we need to return a ptr/len pair, but wasm currently only supports one return value (multiple return values is being standardized). To work around this in the meantime, we're actually returning a pointer to a ptr/len pair, and then using functions to access the various fields.

  • Some cleanup ends up happening in wasm. The __wbindgen_boxed_str_free function is used to free the return value of greet after it's been decoded onto the JS heap (using TextDecoder). The __wbindgen_free is then used to free the space we allocated to pass the string argument once the function call is done.

Next let's take a look at the Rust side of things as well. Here we'll be looking at a mostly abbreviated and/or "simplified" in the sense of this is what it compiles down to:


# #![allow(unused_variables)]
#fn main() {
pub extern "C" fn greet(a: &str) -> String {
    format!("Hello, {}!", a)
}

#[export_name = "greet"]
pub extern "C" fn __wasm_bindgen_generated_greet(
    arg0_ptr: *const u8,
    arg0_len: usize,
) -> *mut String {
    let arg0 = unsafe {
        let slice = ::std::slice::from_raw_parts(arg0_ptr, arg0_len);
        ::std::str::from_utf8_unchecked(slice)
    };
    let _ret = greet(arg0);
    Box::into_raw(Box::new(_ret))
}
#}

Here we can see again that our greet function is unmodified and has a wrapper to call it. This wrapper will take the ptr/len argument and convert it to a string slice, while the return value is boxed up into just a pointer and is then returned up to was for reading via the __wbindgen_boxed_str_* functions.

So in general exporting a function involves a shim both in JS and in Rust with each side translating to or from wasm arguments to the native types of each language. The wasm-bindgen tool manages hooking up all these shims while the #[wasm_bindgen] macro takes care of the Rust shim as well.

Most arguments have a relatively clear way to convert them, bit if you've got any questions just let me know!

Exporting a struct to JS

So far we've covered JS objects, importing functions, and exporting functions. This has given us quite a rich base to build on so far, and that's great! We sometimes, though, want to go even further and define a JS class in Rust. Or in other words, we want to expose an object with methods from Rust to JS rather than just importing/exporting free functions.

The #[wasm_bindgen] attribute can annotate both a struct and impl blocks to allow:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
pub struct Foo {
    internal: i32,
}

#[wasm_bindgen]
impl Foo {
    pub fn new(val: i32) -> Foo {
        Foo { internal: val }
    }

    pub fn get(&self) -> i32 {
        self.internal
    }

    pub fn set(&mut self, val: i32) {
        self.internal = val;
    }
}
#}

This is a typical Rust struct definition for a type with a constructor and a few methods. Annotating the struct with #[wasm_bindgen] means that we'll generate necessary trait impls to convert this type to/from the JS boundary. The annotated impl block here means that the functions inside will also be made available to JS through generated shims. If we take a look at the generated JS code for this we'll see:

import * as wasm from './js_hello_world_bg';

export class Foo {
    static __construct(ptr) {
        return new Foo(ptr);
    }

    constructor(ptr) {
        this.ptr = ptr;
    }

    free() {
        const ptr = this.ptr;
        this.ptr = 0;
        wasm.__wbg_foo_free(ptr);
    }

    static new(arg0) {
        const ret = wasm.foo_new(arg0);
        return Foo.__construct(ret)
    }

    get() {
        const ret = wasm.foo_get(this.ptr);
        return ret;
    }

    set(arg0) {
        const ret = wasm.foo_set(this.ptr, arg0);
        return ret;
    }
}

That's actually not much! We can see here though how we've translated from Rust to JS:

  • Associated functions in Rust (those without self) turn into static functions in JS.
  • Methods in Rust turn into methods in wasm.
  • Manual memory management is exposed in JS as well. The free function is required to be invoked to deallocate resources on the Rust side of things.

To be able to use new Foo(), you'd need to annotate new as #[wasm_bindgen(constructor)].

One important aspect to note here, though, is that once free is called the JS object is "neutered" in that its internal pointer is nulled out. This means that future usage of this object should trigger a panic in Rust.

The real trickery with these bindings ends up happening in Rust, however, so let's take a look at that.


# #![allow(unused_variables)]
#fn main() {
// original input to `#[wasm_bindgen]` omitted ...

#[export_name = "foo_new"]
pub extern "C" fn __wasm_bindgen_generated_Foo_new(arg0: i32) -> u32
    let ret = Foo::new(arg0);
    Box::into_raw(Box::new(WasmRefCell::new(ret))) as u32
}

#[export_name = "foo_get"]
pub extern "C" fn __wasm_bindgen_generated_Foo_get(me: u32) -> i32 {
    let me = me as *mut WasmRefCell<Foo>;
    wasm_bindgen::__rt::assert_not_null(me);
    let me = unsafe { &*me };
    return me.borrow().get();
}

#[export_name = "foo_set"]
pub extern "C" fn __wasm_bindgen_generated_Foo_set(me: u32, arg1: i32) {
    let me = me as *mut WasmRefCell<Foo>;
    ::wasm_bindgen::__rt::assert_not_null(me);
    let me = unsafe { &*me };
    me.borrow_mut().set(arg1);
}

#[no_mangle]
pub unsafe extern "C" fn __wbindgen_foo_free(me: u32) {
    let me = me as *mut WasmRefCell<Foo>;
    wasm_bindgen::__rt::assert_not_null(me);
    (*me).borrow_mut(); // ensure no active borrows
    drop(Box::from_raw(me));
}
#}

As with before this is cleaned up from the actual output but it's the same idea as to what's going on! Here we can see a shim for each function as well as a shim for deallocating an instance of Foo. Recall that the only valid wasm types today are numbers, so we're required to shoehorn all of Foo into a u32, which is currently done via Box (like std::unique_ptr in C++). Note, though, that there's an extra layer here, WasmRefCell. This type is the same as RefCell and can be mostly glossed over.

The purpose for this type, if you're interested though, is to uphold Rust's guarantees about aliasing in a world where aliasing is rampant (JS). Specifically the &Foo type means that there can be as much aliasing as you'd like, but crucially &mut Foo means that it is the sole pointer to the data (no other &Foo to the same instance exists). The RefCell type in libstd is a way of dynamically enforcing this at runtime (as opposed to compile time where it usually happens). Baking in WasmRefCell is the same idea here, adding runtime checks for aliasing which are typically happening at compile time. This is currently a Rust-specific feature which isn't actually in the wasm-bindgen tool itself, it's just in the Rust-generated code (aka the #[wasm_bindgen] attribute).

Importing a function from JS

Now that we've exported some rich functionality to JS it's also time to import some! The goal here is to basically implement JS import statements in Rust, with fancy types and all.

First up, let's say we invert the function above and instead want to generate greetings in JS but call it from Rust. We might have, for example:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen(module = "./greet")]
extern "C" {
    fn greet(a: &str) -> String;
}

fn other_code() {
    let greeting = greet("foo");
    // ...
}
#}

The basic idea of imports is the same as exports in that we'll have shims in both JS and Rust doing the necessary translation. Let's first see the JS shim in action:

import * as wasm from './foo_bg';

import { greet } from './greet';

// ...

export function __wbg_f_greet(ptr0, len0, wasmretptr) {
  const [retptr, retlen] = passStringToWasm(greet(getStringFromWasm(ptr0, len0)));
  (new Uint32Array(wasm.memory.buffer))[wasmretptr / 4] = retlen;
  return retptr;
}

The getStringFromWasm and passStringToWasm are the same as we saw before, and like with __wbindgen_object_drop_ref far above we've got this weird export from our module now! The __wbg_f_greet function is what's generated by wasm-bindgen to actually get imported in the foo.wasm module.

The generated foo.js we see imports from the ./greet module with the greet name (was the function import in Rust said) and then the __wbg_f_greet function is shimming that import.

There's some tricky ABI business going on here so let's take a look at the generated Rust as well. Like before this is simplified from what's actually generated.


# #![allow(unused_variables)]
#fn main() {
extern "C" fn greet(a: &str) -> String {
    extern "C" {
        fn __wbg_f_greet(a_ptr: *const u8, a_len: usize, ret_len: *mut usize) -> *mut u8;
    }
    unsafe {
        let a_ptr = a.as_ptr();
        let a_len = a.len();
        let mut __ret_strlen = 0;
        let mut __ret_strlen_ptr = &mut __ret_strlen as *mut usize;
        let _ret = __wbg_f_greet(a_ptr, a_len, __ret_strlen_ptr);
        String::from_utf8_unchecked(
            Vec::from_raw_parts(_ret, __ret_strlen, __ret_strlen)
        )
    }
}
#}

Here we can see that the greet function was generated but it's largely just a shim around the __wbg_f_greet function that we're calling. The ptr/len pair for the argument is passed as two arguments and for the return value we're receiving one value (the length) indirectly while directly receiving the returned pointer.

Importing a class from JS

Just like with functions after we've started exporting we'll also want to import! Now that we've exported a class to JS we'll want to also be able to import classes in Rust as well to invoke methods and such. Since JS classes are in general just JS objects the bindings here will look pretty similar to the JS object bindings describe above.

As usual though, let's dive into an example!


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen(module = "./bar")]
extern "C" {
    type Bar;

    #[wasm_bindgen(constructor)]
    fn new(arg: i32) -> Bar;

    #[wasm_bindgen(js_namespace = Bar)]
    fn another_function() -> i32;

    #[wasm_bindgen(method)]
    fn get(this: &Bar) -> i32;

    #[wasm_bindgen(method)]
    fn set(this: &Bar, val: i32);

    #[wasm_bindgen(method, getter)]
    fn property(this: &Bar) -> i32;

    #[wasm_bindgen(method, setter)]
    fn set_property(this: &Bar, val: i32);
}

fn run() {
    let bar = Bar::new(Bar::another_function());
    let x = bar.get();
    bar.set(x + 3);

    bar.set_property(bar.property() + 6);
}
#}

Unlike our previous imports, this one's a bit more chatty! Remember that one of the goals of wasm-bindgen is to use native Rust syntax wherever possible, so this is mostly intended to use the #[wasm_bindgen] attribute to interpret what's written down in Rust. Now there's a few attribute annotations here, so let's go through one-by-one:

  • #[wasm_bindgen(module = "./bar")] - seen before with imports this is declare where all the subsequent functionality is imported form. For example the Bar type is going to be imported from the ./bar module.
  • type Bar - this is a declaration of JS class as a new type in Rust. This means that a new type Bar is generated which is "opaque" but is represented as internally containing a JsValue. We'll see more on this later.
  • #[wasm_bindgen(constructor)] - this indicates that the binding's name isn't actually used in JS but rather translates to new Bar(). The return value of this function must be a bare type, like Bar.
  • #[wasm_bindgen(js_namespace = Bar)] - this attribute indicates that the function declaration is namespaced through the Bar class in JS.
  • #[wasm_bindgen(static_method_of = SomeJsClass)] - this attribute is similar to js_namespace, but instead of producing a free function, produces a static method of SomeJsClass.
  • #[wasm_bindgen(method)] - and finally, this attribute indicates that a method call is going to happen. The first argument must be a JS struct, like Bar, and the call in JS looks like Bar.prototype.set.call(...).

With all that in mind, let's take a look at the JS generated.

import * as wasm from './foo_bg';

import { Bar } from './bar';

// other support functions omitted...

export function __wbg_s_Bar_new() {
  return addHeapObject(new Bar());
}

const another_function_shim = Bar.another_function;
export function __wbg_s_Bar_another_function() {
  return another_function_shim();
}

const get_shim = Bar.prototype.get;
export function __wbg_s_Bar_get(ptr) {
  return shim.call(getObject(ptr));
}

const set_shim = Bar.prototype.set;
export function __wbg_s_Bar_set(ptr, arg0) {
  set_shim.call(getObject(ptr), arg0)
}

const property_shim = Object.getOwnPropertyDescriptor(Bar.prototype, 'property').get;
export function __wbg_s_Bar_property(ptr) {
  return property_shim.call(getObject(ptr));
}

const set_property_shim = Object.getOwnPropertyDescriptor(Bar.prototype, 'property').set;
export function __wbg_s_Bar_set_property(ptr, arg0) {
  set_property_shim.call(getObject(ptr), arg0)
}

Like when importing functions from JS we can see a bunch of shims are generated for all the relevant functions. The new static function has the #[wasm_bindgen(constructor)] attribute which means that instead of any particular method it should actually invoke the new constructor instead (as we see here). The static function another_function, however, is dispatched as Bar.another_function.

The get and set functions are methods so they go through Bar.prototype, and otherwise their first argument is implicitly the JS object itself which is loaded through getObject like we saw earlier.

Some real meat starts to show up though on the Rust side of things, so let's take a look:


# #![allow(unused_variables)]
#fn main() {
pub struct Bar {
    obj: JsValue,
}

impl Bar {
    fn new() -> Bar {
        extern "C" {
            fn __wbg_s_Bar_new() -> u32;
        }
        unsafe {
            let ret = __wbg_s_Bar_new();
            Bar { obj: JsValue::__from_idx(ret) }
        }
    }

    fn another_function() -> i32 {
        extern "C" {
            fn __wbg_s_Bar_another_function() -> i32;
        }
        unsafe {
            __wbg_s_Bar_another_function()
        }
    }

    fn get(&self) -> i32 {
        extern "C" {
            fn __wbg_s_Bar_get(ptr: u32) -> i32;
        }
        unsafe {
            let ptr = self.obj.__get_idx();
            let ret = __wbg_s_Bar_get(ptr);
            return ret
        }
    }

    fn set(&self, val: i32) {
        extern "C" {
            fn __wbg_s_Bar_set(ptr: u32, val: i32);
        }
        unsafe {
            let ptr = self.obj.__get_idx();
            __wbg_s_Bar_set(ptr, val);
        }
    }

    fn property(&self) -> i32 {
        extern "C" {
            fn __wbg_s_Bar_property(ptr: u32) -> i32;
        }
        unsafe {
            let ptr = self.obj.__get_idx();
            let ret = __wbg_s_Bar_property(ptr);
            return ret
        }
    }

    fn set_property(&self, val: i32) {
        extern "C" {
            fn __wbg_s_Bar_set_property(ptr: u32, val: i32);
        }
        unsafe {
            let ptr = self.obj.__get_idx();
            __wbg_s_Bar_set_property(ptr, val);
        }
    }
}

impl WasmBoundary for Bar {
    // ...
}

impl ToRefWasmBoundary for Bar {
    // ...
}
#}

In Rust we're seeing that a new type, Bar, is generated for this import of a class. The type Bar internally contains a JsValue as an instance of Bar is meant to represent a JS object stored in our module's stack/slab. This then works mostly the same way that we saw JS objects work in the beginning.

When calling Bar::new we'll get an index back which is wrapped up in Bar (which is itself just a u32 in memory when stripped down). Each function then passes the index as the first argument and otherwise forwards everything along in Rust.

Rust Type conversions

Previously we've been seeing mostly abridged versions of type conversions when values enter Rust. Here we'll go into some more depth about how this is implemented. There are two categories of traits for converting values, traits for converting values from Rust to JS and traits for the other way around.

From Rust to JS

First up let's take a look at going from Rust to JS:


# #![allow(unused_variables)]
#fn main() {
pub trait IntoWasmAbi: WasmDescribe {
    type Abi: WasmAbi;
    fn into_abi(self, extra: &mut Stack) -> Self::Abi;
}
#}

And that's it! This is actually the only trait needed currently for translating a Rust value to a JS one. There's a few points here:

  • We'll get to WasmDescribe later in this section
  • The associated type Abi is what will actually be generated as an argument to the wasm export. The bound WasmAbi is only implemented for types like u32 and f64, those which can be placed on the boundary and transmitted losslessly.
  • And finally we have the into_abi function, returning the Abi associated type which will be actually passed to JS. There's also this Stack parameter, however. Not all Rust values can be communicated in 32 bits to the Stack parameter allows transmitting more data, explained in a moment.

This trait is implemented for all types that can be converted to JS and is unconditionally used during codegen. For example you'll often see IntoWasmAbi for Foo but also IntoWasmAbi for &'a Foo.

The IntoWasmAbi trait is used in two locations. First it's used to convert return values of Rust exported functions to JS. Second it's used to convert the Rust arguments of JS functions imported to Rust.

From JS to Rust

Unfortunately the opposite direction from above, going from JS to Rust, is a bit more complicated. Here we've got three traits:


# #![allow(unused_variables)]
#fn main() {
pub trait FromWasmAbi: WasmDescribe {
    type Abi: WasmAbi;
    unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self;
}

pub trait RefFromWasmAbi: WasmDescribe {
    type Abi: WasmAbi;
    type Anchor: Deref<Target=Self>;
    unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor;
}

pub trait RefMutFromWasmAbi: WasmDescribe {
    type Abi: WasmAbi;
    type Anchor: DerefMut<Target=Self>;
    unsafe fn ref_mut_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor;
}
#}

The FromWasmAbi is relatively straightforward, basically the opposite of IntoWasmAbi. It takes the ABI argument (typically the same as IntoWasmAbi::Abi) and then the auxiliary stack to produce an instance of Self. This trait is implemented primarily for types that don't have internal lifetimes or are references.

The latter two traits here are mostly the same, and are intended for generating references (both shared and mutable references). They look almost the same as FromWasmAbi except that they return an Anchor type which implements a Deref trait rather than Self.

The Ref* traits allow having arguments in functions that are references rather than bare types, for example &str, &JsValue, or &[u8]. The Anchor here is required to ensure that the lifetimes don't persist beyond one function call and remain anonymous.

The From* family of traits are used for converting the Rust arguments in Rust exported functions to JS. They are also used for the return value in JS functions imported into Rust.

Global stack

Mentioned above not all Rust types will fit within 32 bits. While we can communicate an f64 we don't necessarily have the ability to use all the bits. Types like &str need to communicate two items, a pointer and a length (64 bits). Other types like &Closure<Fn()> have even more information to transmit.

As a result we need a method of communicating more data through the signatures of functions. While we could add more arguments this is somewhat difficult to do in the world of closures where code generation isn't quite as dynamic as a procedural macro. Consequently a "global stack" is used to transmit extra data for a function call.

The global stack is a fixed-sized static allocation in the wasm module. This stack is temporary scratch space for any one function call from either JS to Rust or Rust ot JS. Both Rust and the JS shim generated have pointers to this global stack and will read/write information from it.

Using this scheme whenever we want to pass &str from JS to Rust we can pass the pointer as the actual ABI argument and the length is then placed in the next spot on the global stack.

The Stack argument to the conversion traits above looks like:


# #![allow(unused_variables)]
#fn main() {
pub trait Stack {
    fn push(&mut self, bits: u32);
    fn pop(&mut self) -> u32;
}
#}

A trait is used here to facilitate testing but typically the calls don't end up being virtually dispatched at runtime.

Communicating types to wasm-bindgen

The last aspect to talk about when converting Rust/JS types amongst one another is how this information is actually communicated. The #[wasm_bindgen] macro is running over the syntactical (unresolved) structure of the Rust code and is then responsible for generating information that wasm-bindgen the CLI tool later reads.

To accomplish this a slightly unconventional approach is taken. Static information about the structure of the Rust code is serialized via JSON (currently) to a custom section of the wasm executable. Other information, like what the types actually are, unfortunately isn't known until later in the compiler due to things like associated type projections and typedefs. It also turns out that we want to convey "rich" types like FnMut(String, Foo, &JsValue) to the wasm-bindgen CLI, and handling all this is pretty tricky!

To solve this issue the #[wasm_bindgen] macro generates executable functions which "describe the type signature of an import or export". These executable functions are what the WasmDescribe trait is all about:


# #![allow(unused_variables)]
#fn main() {
pub trait WasmDescribe {
    fn describe();
}
#}

While deceptively simple this trait is actually quite important. When you write, an export like this:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
fn greet(a: &str) {
    // ...
}
#}

In addition to the shims we talked about above which JS generates the macro also generates something like:

#[no_mangle]
pub extern "C" fn __wbindgen_describe_greet() {
    <Fn(&str)>::describe();
}

Or in other words it generates invocations of describe functions. In doing so the __wbindgen_describe_greet shim is a programmatic description of the type layouts of an import/export. These are then executed when wasm-bindgen runs! These executions rely on an import called __wbindgen_describe which passes one u32 to the host, and when called multiple times gives a Vec<u32> effectively. This Vec<u32> can then be reparsed into an enum Descriptor which fully describes a type.

All in all this is a bit roundabout but shouldn't have any impact on the generated code or runtime at all. All these descriptor functions are pruned from the emitted wasm file.

js-sys

The js-sys crate provides raw bindings to all the global APIs guaranteed to exist in every JavaScript environment by the ECMAScript standard, and its source lives at wasm-bindgen/crates/js-sys. With the js-sys crate, we can work with Objects, Arrays, Functions, Maps, Sets, etc... without writing the #[wasm_bindgen] imports by hand.

Documentation for this crate will eventually be available on docs.rs but temporarily you can also check out the master branch documentation for the crate.

For example, we can invoke JavaScript Function callbacks and time how long they take to execute with Date.now(), and we don't need to write any JS imports ourselves:


# #![allow(unused_variables)]
#fn main() {
extern crate js_sys;
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn timed(callback: &js_sys::Function) -> f64 {
    let then = js_sys::Date::now();
    callback.apply(JsValue::null(), &js_sys::Array::new()).unwrap();
    let now = js_sys::Date::now();
    now - then
}
#}

The js-sys crate doesn't contain bindings to any Web APIs like document.querySelectorAll. These will be part of the web-sys crate.

Testing

You can test the js-sys crate by running cargo test --target wasm32-unknown-unknown within the crates/js-sys directory in the wasm-bindgen repository:

cd wasm-bindgen/crates/js-sys
cargo test --target wasm32-unknown-unknown

These tests are largely executed in Node.js right now via the wasm-bindgen-test framework

Adding Support for More JavaScript Global APIs

As of 2018-09-24 we've added all APIs in the current ECMAScript standard (yay!). To that end you'll hopefully not find a missing API, but if you do please feel free to file an issue!

We currently add new APIs added to ECMAScript that are in TC39 stage 4 to this crate. If there's a new API in stage 4, feel free to file an issue as well!

Instructions for adding an API

  • [ ] Find the wasm-bindgen issue for the API you'd like to add. If this doesn't exist, feel free to open one! Afterwards be sure to comment on the issue to avoid duplication of work.

  • [ ] Open the MDN page for the relevant JS API.

  • [ ] Open crates/js-sys/src/lib.rs in your editor; this is the file where we are implementing the bindings.

  • [ ] Follow the instructions in the top of crates/js-sys/src/lib.rs about how to add new bindings.

  • [ ] Add a test for the new binding to crates/js-sys/tests/wasm/MyType.rs

  • [ ] Run the JS global API bindings tests

  • [ ] Send a pull request!

web-sys

The web-sys crate provides raw bindings to all of the Web's APIs, and its source lives at wasm-bindgen/crates/web-sys.

The web-sys crate is entirely mechanically generated inside build.rs using wasm-bindgen's WebIDL frontend and the WebIDL interface definitions for Web APIs. This means that web-sys isn't always the most ergonomic crate to use, but it's intended to provide verified and correct bindings to the web platform, and then better interfaces can be iterated on crates.io!

web-sys Overview

The web-sys crate has this file and directory layout:

.
├── build.rs
├── Cargo.toml
├── README.md
├── src
│   └── lib.rs
└── webidls
    └── enabled
        └── ...

webidls/enabled/*.webidl

These are the WebIDL interfaces that we will actually generate bindings for (or at least bindings for some of the things defined in these files).

build.rs

The build.rs invokes wasm-bindgen's WebIDL frontend on all the WebIDL files in webidls/enabled. It writes the resulting bindings into the cargo build's out directory.

src/lib.rs

The only thing src/lib.rs does is include the bindings generated at compile time in build.rs. Here is the whole src/lib.rs file:


# #![allow(unused_variables)]
#fn main() {
//! Raw API bindings for Web APIs
//!
//! This is a procedurally generated crate from browser WebIDL which provides a
//! binding to all APIs that browser provide on the web.
//!
//! This crate by default contains very little when compiled as almost all of
//! its exposed APIs are gated by Cargo features. The exhaustive list of
//! features can be found in `crates/web-sys/Cargo.toml`, but the rule of thumb
//! for `web-sys` is that each type has its own cargo feature (named after the
//! type). Using an API requires enabling the features for all types used in the
//! API, and APIs should mention in the documentation what features they
//! require.

#![doc(html_root_url = "https://docs.rs/web-sys/0.2")]
#![allow(deprecated)]

extern crate js_sys;
extern crate wasm_bindgen;

#[allow(unused_imports)]
use js_sys::Object;

/// Getter for the `Window` object
///
/// [MDN Documentation]
///
/// *This API requires the following crate features to be activated: `Window`*
///
/// [MDN Documentation]: https://developer.mozilla.org/en-US/docs/Web/API/Window
#[cfg(feature = "Window")]
pub fn window() -> Option<Window> {
    use wasm_bindgen::JsCast;

    js_sys::global().dyn_into::<Window>().ok()
}

include!(env!("BINDINGS"));

#}

Cargo features

When compiled the crate is almost empty by default, which probably isn't what you want! Due to the very large number of APIs, this crate uses features to enable portions of its API to reduce compile times. The list of features in Cargo.toml all correspond to types in the generated functions. Enabling a feature enables that type. All methods should indicate what features need to be activated to use the method.

Testing

You can test the web-sys crate by running cargo test within the crates/web-sys directory in the wasm-bindgen repository:

cd wasm-bindgen/crates/web-sys
cargo test --target wasm32-unknown-unknown --all-features

The Wasm tests all run within a headless browser. See the wasm-bindgen-test crate's README.md for details and configuring which headless browser is used.

Logging

The wasm_bindgen_webidl crate (used by web-sys's build.rs) uses env_logger for logging, which can be enabled by setting the RUST_LOG=wasm_bindgen_webidl environment variable while building the web-sys crate.

Make sure to enable "very verbose" output during cargo build to see these logs within web-sys's build script output.

cd crates/web-sys
RUST_LOG=wasm_bindgen_webidl cargo build -vv

If wasm_bindgen_webidl encounters WebIDL constructs that it doesn't know how to translate into wasm-bindgen AST items, it will emit warn-level logs.

WARN 2018-07-06T18:21:49Z: wasm_bindgen_webidl: Unsupported WebIDL interface: ...

Supporting More Web APIs in web-sys

  1. Ensure that the .webidl file describing the interface exists somewhere within the crates/web-sys/webidls/enabled directory.

    First, check to see whether we have the WebIDL definition file for your API:

    grep -rn MyWebApi crates/web-sys/webidls
    
    • If your interface is defined in a .webidl file that is inside the crates/web-sys/webidls/enabled directory, skip to step (3).

    • If your interface isn't defined in any file yet, find the WebIDL definition in the relevant standard and add it as a new .webidl file in crates/web-sys/webidls/enabled. Make sure that it is a standard Web API! We don't want to add non-standard APIs to this crate.

    • If your interface is defined in a .webidl file within any of the crates/web-sys/webidls/unavailable_* directories, you need to move it into crates/web-sys/webidls/enabled, e.g.:

      cd crates/web-sys
      git mv webidls/unavailable_enum_ident/MyWebApi.webidl webidls/enabled/MyWebApi.webidl
      
  2. Verify that the web-sys crate still builds and that its tests still pass with the new .webidl file enabled:

    cd crates/web-sys
    cargo build
    cargo test
    
  3. Verify that bindings are being generated for your new API by generating the documentation and searching for the new API in it:

    cd crates/web-sys
    cargo doc --open
    # search for the new API in the opened docs
    
    • If the new API is not showing up in the docs, rebuild the web-sys crate with logging enabled and look for warning messages that mention your new API. Figure out why bindings weren't generated and then add support to wasm_bindgen_webidl for whatever is needed to generate your API's bindings.

      You might find it helpful to view the generated rust bindings, to see if they are what you would expect. The file will be located at target/wasm32-unknown-unknown/debug/build/web-sys-xxx/out/bindings.rs, where xxx is a combinations of numbers and letters that represents your build. This file is pretty unintelligable until you run rustfmt on it, like rustfmt target/wasm32-unknown-unknown/debug/build/web-sys-xxx/out/bindings.rs.

      There are commented out lines in web-sys/build.rs that run rustfmt as part of the build process, and this can be very helpful for debugging as any error messages with inline code will display it in a readable format.

  4. Add tests for as many of the features in the WebIDL file as possible to crates/web-sys/tests/all/. See the web-sys testing documentation for details.

    Note: Start here at 4 if the WebIDL has already been added but doesn't have full test coverage, then go back to 3 if you find any problems.

  5. If all entities in the WebIDL file have full test coverage, mark the WebIDL script in the README.md file as complete by changing [ ] to [x].

  6. Send a pull request! 😊

Publishing New wasm-bindgen Releases

  1. Compile the publish.rs script:

    rustc publish.rs
    
  2. Bump every crate's minor version:

    # Make sure you are in the root of the wasm-bindgen repo!
    ./publish bump
    
  3. Send a pull request for the version bump.

  4. After the pull request's CI is green and it has been merged, publish to cargo:

    # Make sure you are in the root of the wasm-bindgen repo!
    ./publish publish
    

Team

wasm-bindgen follows the rustwasm organization's governance described here:

  • All pull requests (including those made by a team member) must be approved by at least one other team member.

  • Larger, more nuanced decisions about design, architecture, breaking changes, trade offs, etc are made by team consensus.

Members

alexcrichton fitzgen spastorino ohanar jonathan-s
sendilkumarn belfz afdw