Skip to main content

01. Concepts

About 11 minRustcrashcourserustrswasmassemblyassemblyscriptccpp

01. Concepts 관련


Examples that express some of the major underlying concepts in WebAssembly. Some of these examples are not the most convenient or productive way for building projects with WebAssembly. However, these minimal examples are great for learning, or developing straightforward / lower-level parts of an application.


Introduction

Wasm By Example - Introduction

Introduction

Let's do a brief introduction into major concepts of WebAssembly:

  • WebAssembly is a compile-targeted language for running bytecode on the web.
  • Relative to Javascript, WebAssembly offers predictable performance. It is not inherently faster than Javascript, but it can be faster than JavaScript in the correct use case. Such as computationally intensive tasks, like nested loops or handling large amounts of data. Therefore, WebAssembly is a complement to JavaScript, and not a replacement.
  • WebAssembly is extremely portable. WebAssembly runs on: all major web browsers, V8 runtimes like Node.jsopen in new window, and independent Wasm runtimes like Wasmtimeopen in new window, bytecodealliance/lucetopen in new window, and wasmerio/wasmeropen in new window.
  • WebAssembly has Linear Memory, in other words, one big expandable array. And in the context of Javascript, synchronously accessible by Javascript and Wasm.
  • WebAssembly can export functions and constants, And in the context of Javascript, synchronously accessible by Javascript and Wasm.
  • WebAssembly, in its current MVP, only handles integers and floats. However, tools and libraries exist to make passing high-level data types convenient.

With that, let's take a look at our Hello World to see some of the concepts in action.


Hello World!

Wasm By Example - Hello World!

Hello World!

Before getting started, be sure to check out all of the languages available, by clicking the "languages" dropdown in the header.

Overview

For our first program, we will be doing a "Hello world" type of program in Rustopen in new window and rustwasm/wasm-packopen in new window.

To keep things simple with Wasm's limitations mentioned in the introduction exampleopen in new window, instead of displaying a string, we will add two numbers together and display the result. Though, it is good to keep in mind, in later examples, a lot of these limitations will be abstracted away by your WebAssembly Language of choice (In this case, Rust). It is also highly recommended you take a look at the wasm-pack QuickStart Guideopen in new window, as it will be referenced a lot in this example.

Project Setup

So first, Let's get rust installedopen in new window, which includes cargoopen in new window. Then, using cargo, let's install wasm-pack, which we will need later:

cargo install wasm-pack

Next, let's create our rust crate in our current directory using cargo:

cargo init

Then, let's edit our new Cargo.toml to implement wasm-packopen in new window as mentioned in their quickstart:

[package]
name = "hello-world"
version = "0.1.0"
authors = ["Your Name <your@name.com>"]
edition = "2018"

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

[dependencies]
wasm-bindgen = "0.2"

Lastly, let's take a quick peek inside at the src/ directory. Since we are building a library (lib) to be used by a larger application, we need to rename the src/main.rs to src/lib.rs. Go ahead and do that now before moving forward.

Now that we have our project and environment setup, let's go ahead and start the actual implementation.

Implementation

Let's go ahead and replace src/lib.rs with the required use call as mentioned in the quickstart, as well as our add function:

// The wasm-pack uses wasm-bindgen to build and generate JavaScript binding file.
// Import the wasm-bindgen crate.
use wasm_bindgen::prelude::*;

// Our Add function
// wasm-pack requires "exported" functions
// to include #[wasm_bindgen]
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
  return a + b;
}

Then, let's compile our crate using wasm-pack, into a wasm module. Then run the following command, taking note of the --target webopen in new window. The wasm-pack tool has support for a lot of different output types, especially for bundlers like Webpack or Rollup. But, since we want an ES6 module in our case, we use the web target below:

wasm-pack build --target web

This will output a pkg/ directory containing our wasm module, wrapped in a js object. Next, lets create an index.js JavaScript file, and import the outputted ES6 module in our pkg/ directory. Then, we will call our exported add() function:

Note

In this example, we are using the exported function from the wasm module directly to help highlight the WebAssembly API. wasm-bindgen generates JavaScript bindings code that can be imported as an ES6 import, and is the recommended way to work with your Rust Wasm modules. These JavaScript bindings are shown in the "Passing High Level Data Types with wasm-bindgen" example.

// Import our outputted wasm ES6 module
// Which, export default's, an initialization function
import init from "./pkg/hello_world.js";

const runWasm = async () => {
  // Instantiate our wasm module
  const helloWorld = await init("./pkg/hello_world_bg.wasm");

  // Call the Add function export from wasm, save the result
  const addResult = helloWorld.add(24, 24);

  // Set the result onto the body
  document.body.textContent = `Hello World! addResult: ${addResult}`;
};
runWasm();

Lastly, lets load our ES6 Module, index.js Javascript file in our index.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World - Rust</title>
    <script type="module" src="./index.js"></script>
  </head>
  <body></body>
</html>

And we should have a working Wasm Add (Hello World) program! Congrats!

You should have something similar to the demo (Source Codeopen in new window) below:

Demo

Demo
<html>
<head>
  <meta charset="UTF-8">
  <title>Hello World - Rust</title>
  <link rel="preload" href="./index.js" as="script">
</head>

<body>Hello World! addResult: 48</body>
</html>

Next let's take a deeper look at WebAssembly Exports.


Exports

Wasm By Example - Exports

Exports

Overview

In our Hello World Example, we called a function exported from WebAssembly, in our Javascript. Let's dive a little deeper into exports and how they are used.

Implementation

If you haven't done so already, you should set up your project following the steps laid out in the Hello World Example example.

First, let's add the following to our src/lib.rs file:

// The wasm-pack uses wasm-bindgen to build and generate JavaScript binding file.
// Import the wasm-bindgen crate.
use wasm_bindgen::prelude::*;

// This exports an add function.
// It takes in two 32-bit integer values
// And returns a 32-bit integer value.
#[wasm_bindgen]
pub fn call_me_from_javascript(a: i32, b: i32) -> i32 {
  return add_integer_with_constant(a, b);
}

// A NOT exported constant
// Rust does not support exporting constants
// for Wasm (that I know of).
const ADD_CONSTANT: i32 = 24;

// A NOT exported function
// It takes in two 32-bit integer values
// And returns a 32-bit integer value.
fn add_integer_with_constant(a: i32, b: i32) -> i32 {
  return a + b + ADD_CONSTANT;
}`

Then, let's compile that using rustwasm/wasm-packopen in new window, which will create a pkg/ directory:

wasm-pack build --target web

Next, lets create an index.js file to load and run our wasm output. Let's import the wasm initialization module from pkg/exports.js that was generated by wasm-pack. Then, let's call the module passing in the path to our wasm file at pkg/exports_bg.wasm that was generated by wasm-pack. Then, let's go ahead and call out exported functions, and explore what functions were NOT exported:

Note

In this example, we are using the exported function from the wasm module directly to help highlight the WebAssembly API. wasm-bindgen generates JavaScript bindings code that can be imported as an ES6 import, and is the reccomended way to work with your Rust Wasm modules. These JavaScript bindings are shown in the "Passing High Level Data Types with wasm-bindgen" example.

import wasmInit from "./pkg/exports.js";

const runWasm = async () => {
  // Instantiate our wasm module
  const rustWasm = await wasmInit("./pkg/exports_bg.wasm");

  // Call the Add function export from wasm, save the result
  const result = rustWasm.call_me_from_javascript(24, 24);

  console.log(result); // Should output '72'
  console.log(rustWasm.ADD_CONSTANT); // Should output 'undefined'
  console.log(rustWasm.add_integer_with_constant); // Should output 'undefined'
};
runWasm();

Lastly, lets load our ES6 Module, index.js Javascript file in our index.html. And you should get something similar to the demo (Source Codeopen in new window) below!

Demo

Demo
<html>
<head>
  <meta charset="UTF-8">
  <title>Hello World - Rust</title>
</head>
<body>
  <script type="module" src="./index.js"></script>
  <pre style="border: 1px solid black;">
  DOM Console:
 
  72
   
  undefined
   
  undefined
  </pre>
</body>
</html>

Next let's take a look at WebAssembly Linear Memory.


WebAssembly Linear Memory

Wasm By Example - WebAssembly Linear Memory

WebAssembly Linear Memory

Overview

Another feature of WebAssembly, is its linear memory. Linear memory is a continuous buffer of unsigned bytes that can be read from and stored into by both Wasm and Javascript. In other words, Wasm memory is an expandable array of bytes that Javascript and Wasm can synchronously read and modify. Linear memory can be used for many things, one of them being passing values back and forth between Wasm and Javascript.

In rust, tools like rustwasm/wasm-bindgenopen in new window, which is part of wasm-pack workflow, abstracts away linear memory, and allows using native data structures between rust and Javascript. But for this example, we will use simple byte (Unsigned 8-bit integer) buffers and pointers (Wasm memory array indexes) as a simple(r) way to pass memory back and forth, and show off the concept.

Let's see how we can use linear memory:

Implementation

First, let's add the following to our src/lib.rs file:

// The wasm-pack uses wasm-bindgen to build and generate JavaScript binding file.
// Import the wasm-bindgen crate.
use wasm_bindgen::prelude::*;

// Create a static mutable byte buffer.
// We will use for passing memory between js and wasm.
// NOTE: global `static mut` means we will have "unsafe" code
// but for passing memory between js and wasm should be fine.
const WASM_MEMORY_BUFFER_SIZE: usize = 2;
static mut WASM_MEMORY_BUFFER: [u8; WASM_MEMORY_BUFFER_SIZE] = [0; WASM_MEMORY_BUFFER_SIZE];

// Function to store the passed value at index 0,
// in our buffer
#[wasm_bindgen]
pub fn store_value_in_wasm_memory_buffer_index_zero(value: u8) {
  unsafe {
    WASM_MEMORY_BUFFER[0] = value;
  }
}

// Function to return a pointer to our buffer
// in wasm memory
#[wasm_bindgen]
pub fn get_wasm_memory_buffer_pointer() -> *const u8 {
  let pointer: *const u8;
  unsafe {
    pointer = WASM_MEMORY_BUFFER.as_ptr();
  }

  return pointer;
}

// Function to read from index 1 of our buffer
// And return the value at the index
#[wasm_bindgen]
pub fn read_wasm_memory_buffer_and_return_index_one() -> u8 {
  let value: u8;
  unsafe {
    value = WASM_MEMORY_BUFFER[1];
  }
  return value;
}

Then, let's compile that using rustwasm/wasm-packopen in new window, which will create a pkg/ directory:

wasm-pack build --target web

Next, lets create an index.js file to load and run our wasm output. Let's import the wasm initialization module from pkg/webassembly_linear_memory.js that was generated by wasm-pack. Then, let's call the module passing in the path to our wasm file at pkg/webassembly_linear_memory_bg.wasm that was generated by wasm-pack. Then, let's go ahead and read and write to memory from both Wasm and JS. Please read through the commented code for context. And be sure to read the note at the bottom of this code example. Let's dive into our resulting index.js:

Note

In this example, we are using the exported function from the wasm module directly to help highlight the WebAssembly API. wasm-bindgen generates JavaScript bindings code that can be imported as an ES6 import, and is the reccomended way to work with your Rust Wasm modules. These JavaScript bindings are shown in the "Passing High Level Data Types with wasm-bindgen" example.

const runWasm = async () => {
  const rustWasm = await wasmInit("./pkg/webassembly_linear_memory_bg.wasm");

  /**
   * Part one: Write in Wasm, Read in JS
   */
  console.log("Write in Wasm, Read in JS, Index 0:");

  // First, let's have wasm write to our buffer
  rustWasm.store_value_in_wasm_memory_buffer_index_zero(24);

  // Next, let's create a Uint8Array of our wasm memory
  let wasmMemory = new Uint8Array(rustWasm.memory.buffer);

  // Then, let's get the pointer to our buffer that is within wasmMemory
  let bufferPointer = rustWasm.get_wasm_memory_buffer_pointer();

  // Then, let's read the written value at index zero of the buffer,
  // by accessing the index of wasmMemory[bufferPointer + bufferIndex]
  console.log(wasmMemory[bufferPointer + 0]); // Should log "24"

  /**
   * Part two: Write in JS, Read in Wasm
   */
  console.log("Write in JS, Read in Wasm, Index 1:");

  // First, let's write to index one of our buffer
  wasmMemory[bufferPointer + 1] = 15;

  // Then, let's have wasm read index one of the buffer,
  // and return the result
  console.log(rustWasm.read_wasm_memory_buffer_and_return_index_one()); // Should log "15"

  /**
   * NOTE: if we were to continue reading and writing memory,
   * depending on how the memory is grown by rust, you may have
   * to re-create the Uint8Array since memory layout could change.
   * For example, `let wasmMemory = new Uint8Array(rustWasm.memory.buffer);`
   * In this example, we did not, but be aware this may happen :)
   */
};
runWasm();

Lastly, lets load our ES6 Module, index.js Javascript file in our index.html. And you should get something similar to the demo (Source Codeopen in new window) below!

Demo

Demo
<html>
<head>
  <meta charset="UTF-8">
  <title>Hello World - Rust</title>
  <link rel="preload" href="./index.js" as="script">
</head>
<body>
  <script type="module" src="./index.js"></script>


  <pre style="border: 1px solid black;">
  DOM Console:
 
  Write in Wasm, Read in JS, Index 0:
   
  24
   
  Write in JS, Read in Wasm, Index 1:
   
  15
  </pre>
</body>
</html>

Next let's take a look at importing JavaScript functions into WebAssembly.


Importing Javascript Functions Into WebAssembly

Wasm By Example - Importing Javascript Functions Into WebAssembly

Importing Javascript Functions Into WebAssembly

Overview

When you are instantiating Wasm modules, you are able to pass in an importObjectopen in new window. This importObject can be used to call host (Javascript) functions within Wasm!

In rust, tools like rustwasm/wasm-bindgenopen in new window, which is part of wasm-pack workflow, abstracts away the importObject.

In this example, we will import and implement a simple console.log which is called within Wasm. This example is inspired by the rustwasm/wasm-bindgen - console_log exampleopen in new window , but simplified. So let's jump into the example:

Implementation

First, let's add the following to our src/lib.rs file:

// The wasm-pack uses wasm-bindgen to build and generate JavaScript binding file.
// Import the wasm-bindgen crate.
use wasm_bindgen::prelude::*;

// Let's define our external function (imported from JS)
// Here, we will define our external `console.log`
#[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);
}

// Export a function that will be called in JavaScript
// but call the "imported" console.log.
#[wasm_bindgen]
pub fn console_log_from_wasm() {
  log("This console.log is from wasm!");
}

Then, let's compile that using wasm-pack, which will create a pkg/ directory:

wasm-pack build --target web

Next, lets create an index.js file to load and run our wasm output. Let's import the wasm initialization module from pkg/importing_javascript_functions_into_webassembly.js that was generated by wasm-pack. Then, let's call the module passing in the path to our wasm file at pkg/importing_javascript_functions_into_webassembly_bg.wasm that was generated by wasm-pack. Then, we will go ahead and call our exported wasm function, that will call the "imported" console.log JavaScript function:

Note

In this example, we are using the exported function from the wasm module directly to help highlight the WebAssembly API. wasm-bindgen generates JavaScript bindings code that can be imported as an ES6 import, and is the reccomended way to work with your Rust Wasm modules. These JavaScript bindings are shown in the "Passing High Level Data Types with wasm-bindgen" example.

const runWasm = async () => {
  // Instantiate our wasm module
  const rustWasm = await wasmInit(
    "./pkg/importing_javascript_functions_into_webassembly_bg.wasm"
  );

  // Run the exported function
  rustWasm.console_log_from_wasm(); // Should log "This console.log is from wasm!"
};
runWasm();

Lastly, lets load our ES6 Module, index.js Javascript file in our index.html. And you should get something similar to the demo (Source Code) below!

Demo

And that's it for the basics! Next, lets took a look at some "Advanced Web Demos", with an example of Reading and Writing Graphics with WebAssembly.

Demo
<html>
<head>
  <meta charset="UTF-8">
  <title>Hello World - Rust</title>
  <link rel="preload" href="./index.js" as="script">
</head>
<body>
  <script type="module" src="./index.js"></script>
  <p><b>Please check your JavaScript console for the results.</b></p>

</body>
</html>
console.log('This console.log is from wasm!')

이찬희 (MarkiiimarK)
Never Stop Learning.