Skip to content
Get println! & panics to work in WebAssembly
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
example
src
.gitignore
Cargo.lock
Cargo.toml
LICENSE
README.md

README.md

wasm-glue

Get println! & panics to work in WebAssembly

WebAssembly is cool and all, but Rust println!'s don't work out of the box, and when it crashes you're left with that unhelpful "unreachable" error in the stack trace instead of it telling you what actually went wrong.

wasm-glue is glue code to hook up your stdout and stderr.

👉 Live Example


Usage

Most basic usage, call wasm_glue::hook() once, near the start of whatever you are doing:

extern crate wasm_glue;

#[no_mangle]
pub fn run_webassembly() {
    // hook stdout and stderr up once, before printing anything
    wasm_glue::hook();

    println!("hello console!");
    println!("I'm gunna crash:");

    None::<Option<u32>>.unwrap();
}

Coordinating JavaScript: You'll need 3 imported JavaScript functions for this to work: print, eprint, and trace:

extern {
    fn print(ptr: *const c_char);  // for stdout
    fn eprint(ptr: *const c_char); // for stderr
    fn trace(ptr: *const c_char);  // specifically for panics
}

A basic implementation of these functions would look like this:

// keep a WebAssembly memory reference for `readString`
let memory;

// read a null terminated c string at a wasm memory buffer index
function readString(ptr) {
  const view = new Uint8Array(memory.buffer);

  let end = ptr;
  while (view[end]) ++end;

  const buf = new Uint8Array(view.subarray(ptr, end));
  return (new TextDecoder()).decode(buf);
}

// `wasm_glue::hook()` requires all three
const imports = {
  env: {
    print(ptr) {
      console.log(readString(ptr));
    },

    eprint(ptr) {
      console.warn(readString(ptr));
    },

    trace(ptr) {
      throw new Error(readString(ptr));
    },
  },
};

// ...

WebAssembly.instantiate(buffer, imports).then((result) => {
    const exports = result.instance.exports;

    // update memory reference for readString()
    memory = exports.memory;
    exports.run_webassembly();

    // ...
})

💥 Boom! Fully working println!'s and helpful panics.

See a complete example of this in the /example folder.

Extra Credit: demangle your stack traces.
You can copy the implementation here to demangle the .stack property of the error you generate inside your trace function. Helps make debugging a little more readable.


What's happening?

wasm-glue uses somewhat obscure std library functions:

  • std::io::set_print()
  • std::io::set_panic()
  • std::panic::set_hook()

Check lib.rs to see what's going on.


Options

wasm_glue::hook() calls all 3 of those magic functions. But you can pick and choose if you'd rather. You can also set stdout / stderr to unbuffered instead of line buffered (the default).

::set_stdout()
::set_stdout_unbuffered()
::set_stderr()
::set_stderr_unbuffered()
::set_panic_hook()

Alternatively, you can just use the macros for print! / eprint:

#[macro_use]
extern crate wasm_glue;

License

MIT

You can’t perform that action at this time.