A Introduction to Rust  & Web Assembly for Javascript Developers!

Josh Finnie, Rustacean 

Talk Outline

  • What is Rust?
  • What is Web Assembly?
  • How do the two work together?
  • Conway's Game of Like
  • WASM-frontmatter

About Me

  • Web developer for about 10 years
  • Work at PBS focusing on Python and Javascript
  • Really like building command-line applications
  • Currently having a huge crush on Rust

What is Rust?

Rust

Rust is a  programming language designed for performance and safety, especially safe
concurrency.

Rust is syntactically
similar to C++, but can guarantee memory safety by using a borrow checker to validate references. [1]

[1] https://en.wikipedia.org/wiki/Rust_(programming_language)

But what is Rust, to me...

  • A natural progression from starting to learn to love typed languages.
    • I have been using Typescript for a while now and have loved it!
  • A language that gives me a feeling of having some "low level" chops.
    • There's a lot of languages here in this category; Go, Rust, C++. I have the most success with adopting Rust into my daily coding repertoire.

Some Pros of Rust

  • Strong community support with a good ecosystem of third party libraries.
    • Check out  https://crates.io/ (Rust's NPM)
  • Strong internal "batteries-included" command line tools including formatter, linter, type-checker and robust language server.
  • Easy to understand syntax (for me easier to understand that Go) with a well-defined best-practice patterns.

Some Cons of Rust

  • Documentation for some crates can be severely lacking or out-of-date.
  • Can be difficult to get the stars to aline if coming from Python or Javascript.
  • Compiling your code when using many crates can be a bit slow for rapid development.
  • Newish version of Rust (Rust 2018) can lead to some Python2/Python3 incompatibility issues if learning through online examples.

HelloWorld.rs

fn main() {
    // Special formatting can be specified after a `:`.
    println!("{} of {:b} people know binary, the other half doesn't", 1, 2);

    // structs do not have access to everything by default,
    // need to implement how Point is displayed to println
    struct Point {
        x: i32,
        y: i32,
    }
    impl std::fmt::Display for Point {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "({}, {})", self.x, self.y)
        }
    }
    let origin = Point { x: 1, y: 10 };
    println!("The origin is: {}", origin);
}

HelloWorld.rs

$ cargo run helloWorld.rs
1 of 10 people know binary, the other half doesn't
The origin is: (1, 10)

What is Web Assembly?

WASM

WebAssembly (WASM) is an open web standard that defines a portable binary-code format for executable programs in the web giving near-native code execution speed in the web browser. [1]

[1] https://en.wikipedia.org/wiki/WebAssembly

What is WASM to me...

  • A tool that allows me to speed up my javascript.
  • A path to highly-performant web computing.
  • A tool to make me realize I don't miss the idea of writing Assembly Language at University.

Some pros of WASM

  • Allows me to write hyper efficient programming algorithms to edge out microseconds of efficiencies.
  • Part of the open web and supported on most browsers and supported within Node.js.
  • Highly Secure and portable.
  • Can handle more intense data manipulation than JS

 

You can read the specification here: https://webassembly.github.io/spec/core/intro/introduction.html

Some cons of WASM

  • Separation of concerns from your actual codebase to what is being executed IRL
  • Difficulty to debug
  • Difficulty in passing data back and forth between Javascript and WASM
  • You don't rid yourself of the Javascript ecosystem by moving to WASM (JS is still needed to load the WASM module into browsers currently)

helloWorld.wasm

// helloWorld.wat
(module
  (func (export "add") (param $n1 i32) (param $n2 i32) (result i32)
    get_local $n1
    get_local $n2
    i32.add
  )
)

// helloWorld.wasm
00 61 73 6d 01 00 00 00 01 07 01 60 02 7f 7f 01
7f 03 02 01 00 07 07 01 03 61 64 64 00 00 0a 09
01 07 00 20 00 20 01 6a 0b

// helloWorld.mjs
import * as M from './add.wasm';

console.log(M.add(10, 13)); // 23

helloWorld.wasm

$ node --experimental-modules --experimental-wasm-modules helloWorld.mjs
23

Note: This example is writing WebAssembly Text Format code from scratch and is a bit silly... I just wanted to show it to you for completeness. Read more here: https://dev.to/sendilkumarn/loading-wasm-as-esm-in-nodejs-2gdb

Languages

There are quite a few languages that can compile into WASM if Rust is not your cup of tea:

  • C / C++
  • C#
  • Go
  • Python*

* 😬

Web Assembly System Interface

Not only is WASM APIs existing in browsers, but there has been some work to get WASM running on servers. This is beyond the scope of this talk, but it's an interesting point to bring up.

It makes the general idea of adopting WASM into your workflow a bit more appetizing. For more information check out the work done on WASI.

 

https://wasi.dev/

Web Assembly System Interface

use std::env;
use std::fs;
use std::io::{Read, Write};

fn process(input_fname: &str, output_fname: &str) -> Result<(), String> {
    let mut input_file =
        fs::File::open(input_fname).map_err(|err| format!("error opening input: {}", err))?;
    let mut contents = Vec::new();
    input_file
        .read_to_end(&mut contents)
        .map_err(|err| format!("read error: {}", err))?;

    let mut output_file = fs::File::create(output_fname)
        .map_err(|err| format!("error opening output '{}': {}", output_fname, err))?;
    output_file
        .write_all(&contents)
        .map_err(|err| format!("write error: {}", err))
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    if args.len() < 3 {
        eprintln!("{} <input_file> <output_file>", program);
        return;
    }

    if let Err(err) = process(&args[1], &args[2]) {
        eprintln!("{}", err)
    }
}

$ rustup target add wasm32-wasi
$ cargo build --target wasm32-wasi
$ wasmtime --dir=. --dir=/tmp demo.wasm test.txt /tmp/somewhere.txt

How do they work together?

After seeing an example of writing Web Assembly from scratch, I want to introduce you to using Rust!

 

Surprise!

The Rust WASM Glue

There are two main crates that combine to make your life easy. These two crates are:

wasm-bindgen

wasm-bindgen is a crate that facilitates high-level interactions between WASM modules and Javascript. [1]

 

In other words, wasm-bindgen allows you to import Javascript things to Rust and export Rust things to Javascript without worrying too much about the complexity of doing so.

 

[1] https://github.com/rustwasm/wasm-bindgen

wasm-bindgen

// module.rs
use wasm_bindgen::prelude::*;

// Import the `window.alert` function from the Web.
#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

// Export a `greet` function from Rust to JavaScript, that alerts a
// hello message.
#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

// module.js
import { greet } from "./hello_world";

greet("World!");

wasm-bindgen

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 a circle.
    context
        .arc(75.0, 75.0, 50.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    context.stroke();
}

wasm-pack

wasm-pack is a command-line application that seeks to be your one-stop-shop for building WASM packages generated by Rust. [1]

 

This tool simplifies the infrastructure required to get your Rust code compiled into WASM, gives you a nice Javascript wrapper class and allows you to publish to NPM all with a few commands, it's invaluable!

 

[1] https://github.com/rustwasm/wasm-pack

wasm-pack

wasm-pack

The wasm-pack build command is super powerful and really removes a lot of issues you'd have with other languages. Below are some flags that are indispensable.

  • --target
    • this flag will customize the Javascript that is emitted and how the WASM file interacts.
  • --profile
    • controls your debug state and performance compilation.

Conway's Game of Life

This is the Rust/WASM Hello World example. You can find the tutorial for this here:

https://rustwasm.github.io/docs/book/game-of-life/introduction.html

 

Conway's Game of Life

Conway's Game of Life

  • The Javascript file importing the WASM code:
  • The Rust code that generates the WASM code:

wasm-frontmatter

This is a library that I wrote to understand the benefits of using WASM for command line applications in Node.js.

https://github.com/joshfinnie/wasm-frontmatter

 

wasm-frontmatter

Gray-Matter runtime: 3.859ms
WASM-Frontmatter runtime: 0.694ms

Gray-Matter output
{
  content: '\nThis is an excerpt!\n\n---\n\n## Blog\n\nThis is a blog post!',
  data: {
    title: 'blog post',
    categories: [ 'wasm', 'markdown', 'test' ]
  },
  excerpt: '\nThis is an excerpt!\n\n'
}

WASM-Frontmatter output
{
  content: '\nThis is an excerpt!\n\n---\n\n## Blog\n\nThis is a blog post!',
  data: {
    title: 'blog post',
    categories: [ 'wasm', 'markdown', 'test' ]
  },
  excerpt: '\nThis is an excerpt!\n\n'
}

WASM-frontmatter

  • The Rust code that generates the WASM code:

 

Live Demo!

WASM-frontmatter

Next steps for WASM-frontmatter:

  • Figure out the best practices for WASM optimization.
    • Might try wasm-opt from https://github.com/webassembly/binaryen
  • Allow users to pass in a Javascript callback when defining the excerpt flag.
    • This is an option on gray-matter, and would love to see it happen for WASM-frontmatter.

Thank You!

Questions?

Josh Finnie

@joshfinnie (almost everywhere)

Intro to Rust & WASM for JS Developers

By Josh Finnie

Intro to Rust & WASM for JS Developers

  • 1,233