knowledge/technology/dev/WebAssembly.md
2024-03-27 11:51:40 +01:00

8.1 KiB

obj mime extension website rev
concept application/wasm wasm https://webassembly.org 2024-03-26

WebAssembly

WebAssembly, often abbreviated as Wasm, is an open standard and binary instruction format designed for a stack-based virtual machine. It aims to execute code efficiently across various platforms, including web browsers. WebAssembly is a portable binary instruction format that serves as a compilation target for high-level programming languages like C, C++, and Rust. It enables these languages to be executed in web browsers at near-native speeds, bridging the performance gap between web applications and native applications.

Features of WebAssembly

1. Efficiency

  • WebAssembly binaries are designed to be compact and fast to load, enabling efficient transmission over the network.
  • It leverages Just-In-Time (JIT) compilation techniques for optimal runtime performance.

2. Safety

  • WebAssembly runs in a sandboxed environment within the browser, providing a high level of security.
  • It uses a memory-safe, sandboxed execution model to prevent malicious code from compromising the host environment.

3. Compatibility

  • WebAssembly is designed to be platform-independent, allowing code to run consistently across different architectures and operating systems.
  • It integrates seamlessly with existing web technologies like JavaScript, enabling interoperability with web applications.

4. Versatility

  • WebAssembly can be used for a variety of use cases beyond web development, including server-side applications, game engines, and multimedia processing.

How WebAssembly Works

WebAssembly code is typically generated by compiling source code written in languages like C, C++, or Rust using specific compilers such as Emscripten or the Rust compiler with the wasm32-unknown-unknown target.

Once compiled, the WebAssembly code can be executed within a web browser using the JavaScript-based WebAssembly runtime. Alternatively, it can be run outside the browser using standalone WebAssembly runtimes or integrated into existing software systems.

Use Cases of WebAssembly

WebAssembly has various applications across different domains:

  • Web Development: Enhancing web applications with high-performance computations, multimedia processing, and gaming.
  • Server-Side Applications: Running compute-intensive tasks efficiently on the server.
  • Game Development: Building cross-platform games with near-native performance.
  • Multimedia Processing: Processing and manipulating multimedia content such as images, audio, and video in the browser.
  • Blockchain: Executing smart contracts and decentralized applications (DApps) on blockchain platforms.

Usage with Rust

Requirements

You need Rust installed and the following for creating the WebAssembly:

cargo install wasm-pack

Creating the project

Create a new library:

cargo new --lib hello-wasm

Sample src/lib.rs:

use wasm_bindgen::prelude::*;

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

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

wasm-pack uses wasm-bindgen, another tool, to provide a bridge between the types of JavaScript and Rust. It allows JavaScript to call a Rust API with a string, or a Rust function to catch a JavaScript exception.

Calling external functions in JavaScript from Rust

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

As you might suspect, this is the alert function provided by JavaScript.

Whenever you want to call JavaScript functions, you can add them to this file, and wasm-bindgen takes care of setting everything up for you. Not everything is supported yet, but we're working on it.

Producing Rust functions that JavaScript can call

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

Once again, we see the #[wasm_bindgen] attribute. In this case, it's not modifying an extern block, but a fn; this means that we want this Rust function to be able to be called by JavaScript. It's the opposite of extern: these aren't the functions we need, but rather the functions we're giving out to the world.

This function is named greet, and takes one argument, a string (written &str), name. It then calls the alert function we asked for in the extern block above.

Compiling our code to WebAssembly

To compile our code correctly, we first need to configure it with Cargo.toml. Open this file, and change its contents to look like this:

[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
repository = "https://github.com/yourgithubusername/hello-wasm"
edition = "2018"

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

[dependencies]
wasm-bindgen = "0.2"

The big part to add is the [package]. The [lib] part tells Rust to build a cdylib version of our package.
The last section is the [dependencies] section. Here's where we tell Cargo what version of wasm-bindgen we want to depend on.

Building the package

Now that we've got everything set up, let's build the package. We'll be using the generated code in a native ES module and in Node.js. For this purpose, we'll use the --target argument in wasm-pack build to specify what kind of WebAssembly and JavaScript is generated.

wasm-pack build --target web

This does a number of things (and they take a lot of time, especially the first time you run wasm-pack). In short, wasm-pack build:

  • Compiles your Rust code to WebAssembly.
  • Runs wasm-bindgen on that WebAssembly, generating a JavaScript file that wraps up that WebAssembly file into a module the browser can understand.
  • Creates a pkg directory and moves that JavaScript file and your WebAssembly code into it.
  • Reads your Cargo.toml and produces an equivalent package.json.
  • Copies your README.md (if you have one) into the package.

Using the package on the web

Now that we've got a compiled Wasm module, let's run it in the browser. Let's start by creating a file named index.html in the root of the project, so we end up with the following project structure:

├── Cargo.lock
├── Cargo.toml
├── index.html  <-- new index.html file
├── pkg
│   ├── hello_wasm.d.ts
│   ├── hello_wasm.js
│   ├── hello_wasm_bg.wasm
│   ├── hello_wasm_bg.wasm.d.ts
│   └── package.json
├── src
│   └── lib.rs
└── target
    ├── CACHEDIR.TAG
    ├── release
    └── wasm32-unknown-unknown

Put the following content in the index.html file:

<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <title>hello-wasm example</title>
  </head>
  <body>
    <script type="module">
      import init, { greet } from "./pkg/hello_wasm.js";
      init().then(() => {
        greet("WebAssembly");
      });
    </script>
  </body>
</html>

The script in this file will import the JavaScript glue code, initialize the Wasm module, and call the greet function we wrote in Rust.

Serve the root directory of the project with a local web server, (e.g. python3 -m http.server).

Note: Make sure to use an up-to-date web server that supports the application/wasm MIME type. Older web servers might not support it yet.

Load index.html from the web server. An alert box appears on the screen, with Hello, WebAssembly! in it. We've successfully called from JavaScript into Rust, and from Rust into JavaScript.