JavaScript Interoperation - Rust and WebAssembly (original) (raw)

  1. 1. Introduction
  2. 2. Why Rust and WebAssembly?
  3. 3. Background And Concepts
    1. 3.1. What is WebAssembly?
  4. 4. Tutorial
    1. 4.1. Setup
    2. 4.2. Hello, World!
    3. 4.3. Rules
    4. 4.4. Implementing Life
    5. 4.5. Testing Life
    6. 4.6. Debugging
    7. 4.7. Adding Interactivity
    8. 4.8. Time Profiling
    9. 4.9. Shrinking .wasm Size
    10. 4.10. Publishing to npm
  5. 5. Reference
    1. 5.1. Crates You Should Know
    2. 5.2. Tools You Should Know
    3. 5.3. Project Templates
    4. 5.4. Debugging
    5. 5.5. Time Profiling
    6. 5.6. Shrinking .wasm Size
    7. 5.7. JavaScript Interoperation
    8. 5.8. Which Crates Will Work Off-the-Shelf with WebAssembly?
    9. 5.9. How to Add WebAssembly Support to a General-Purpose Crate
    10. 5.10. Deploying Rust and WebAssembly to Production

Rust and WebAssembly

JavaScript Interoperation Importing and Exporting JS Functions From the Rust Side

When using wasm within a JS host, importing and exporting functions from the Rust side is straightforward: it works very similarly to C.

WebAssembly modules declare a sequence of imports, each with a _module name_and an import name. The module name for an extern { ... } block can be specified using #[link(wasm_import_module)], currently it defaults to "env".

Exports have only a single name. In addition to any extern functions the WebAssembly instance's default linear memory is exported as "memory".


# #![allow(unused_variables)]
#fn main() {
// import a JS function called `foo` from the module `mod`
#[link(wasm_import_module = "mod")]
extern { fn foo(); }

// export a Rust function called `bar`
#[no_mangle]
pub extern fn bar() { /* ... */ }
#}

Because of wasm's limited value types, these functions must operate only on primitive numeric types.

From the JS Side

Within JS, a wasm binary turns into an ES6 module. It must be _instantiated_with linear memory and have a set of JS functions matching the expected imports. The details of instantiation are available on MDN.

The resulting ES6 module will contain all of the functions exported from Rust, now available as JS functions.

Here is a very simple example of the whole setup in action.

Going Beyond Numerics

When using wasm within JS, there is a sharp split between the wasm module's memory and the JS memory:

Thus, sophisticated interop happens in two main ways:

Fortunately, this interop story is very amenable to treatment through a generic "bindgen"-style framework: wasm-bindgen. The framework makes it possible to write idiomatic Rust function signatures that map to idiomatic JS functions, automatically.

Custom Sections

Custom sections allow embedding named arbitrary data into a wasm module. The section data is set at compile time and is read directly from the wasm module, it cannot be modified at runtime.

In Rust, custom sections are static arrays ([T; size]) exposed with the#[link_section] attribute:


# #![allow(unused_variables)]
#fn main() {
#[link_section = "hello"]
pub static SECTION: [u8; 24] = *b"This is a custom section";
#}

This adds a custom section named hello to the wasm file, the rust variable name SECTION is arbitrary, changing it wouldn't alter the behaviour. The contents are bytes of text here but could be any arbitrary data.

The custom sections can be read on the JS side using theWebAssembly.Module.customSections function, it takes a wasm Module and the section name as arguments and returns an Array of ArrayBuffers. Multiple sections may be specified using the same name, in which case they will all appear in this array.

WebAssembly.compileStreaming(fetch("sections.wasm"))
.then(mod => {
  const sections = WebAssembly.Module.customSections(mod, "hello");

  const decoder = new TextDecoder();
  const text = decoder.decode(sections[0]);

  console.log(text); // -> "This is a custom section"
});