Introduction to WebAssembly(Wasm) with Rust for Beginners
Practical Introduction of WebAssembly/Wasm to Beginners with Rust Programming using wasm-pack and wasm-bindgen crates.
Introduction
WebAssemly also known as WASM, is a technology that enables developers to run a set of code on a web browser with native-like performance. It is low-level assembly-like code that runs in the browser alongside JavaScript to achieve performance.
As we all know in terms of performance lower level languages are the fastest like Binary, Assembly language, Higher level languages like C, C++, JavaScript, Python, Java, Ruby, Rust, Swift, and all that most of us use for daily purpose programming. Because these are more human-friendly than machines. There are a set of tools like interpreters, compilers, assemblers and others which convert these languages into machine code to make this understandable to real hardware.
There are programming languages in which we can write our code and ship WebAssembly or Binary(WASM) files. Majorly used ones are Rust, TinyGo(Go), Emscripten(C/C++), and AssemblyScript. All of these languages are known for better performance and memory optimization.
We will use Rust language as a compilation target for our demo of programming.
Demo
Installation
cargo install wasm-pack
In this cargo is a Rust package manager just like npm in node. In Rust, libraries are known as crates like packages in Node. Here above command will install (wasm-pack)[github.com/rustwasm/wasm-pack] crate/lib in our system.
Initialization
Create a new directory and go to that directory.
mkdir wasm-rust-demo && cd wasm-rust-demo
Create Cargo Package.
cargo init
Above command will create Cargo.toml manifest file(similar like package.json in node) and src directory with main.rs basic rust file.
Edit Cargo.toml
Paste the below code in the Cargo.toml file. It defines crate-type as cdylib, which is used when compiling a dynamic library which needs to be loaded from another language like JavaScript in our case. It includes dependency as wasm-bindgen. This crate/package provides communication between Rust and JavaScript with other features.
[package]
name = "wasm-rust-demo"
version = "0.1.0"
authors = ["Piyush Goyani <thesourcepedia@gmail.com>"]
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
Go to the src/ directory, rename main.rs to lib.rs, and add the following content.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn calc(a: i32, b: i32, op: char) -> i32 {
if op == '+' {
return a + b;
} else if op == '-' {
return a - b;
} else if op == '*' {
return a * b;
} else if op == '/' {
return a / b;
} else {
return 0;
}
}
Here we are defining a Rust function named calc which takes three parameters, two numbers and 3rd for an operator to perform math action and return the result.
Build
wasm-pack build --target web
This command will compile Rust code to wasm and generate a pkg directory which can be published to npm as a package and provides files which can be used in JavaScript communication.
Error during build
[INFO]: Installing wasm-bindgen...
Error: failed to download from github.com/WebAssembly/binaryen/releases/do..
To disable
wasm-opt
, addwasm-opt = false
to your package metadata in yourCargo.toml
.
Add this line in Cargo.toml
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
Running WebAssembly/WASM Code.
1. By Loading Generated JS package
<script type="module">
import init, { calc } from "./pkg/wasm_with_rust.js"
init().then(() => {
console.log(calc(10, 2, "+")) // 12
console.log(calc(10, 2, "-")) // 8
console.log(calc(10, 2, "*")) // 20
console.log(calc(10, 2, "/")) // 5
})
</script>
In this, we are loading a javascript file which is generated from wasm-pack which does all the job of loading WebAssembly code and all complex memory-related stuff and export functions that we wrote in Rust(e.g calc()) and init() to initialize WebAssembly. In this, we are passing two integer parameters and 3rd is an operator which decides which math operation to perform.
2. By Directly loading .wasm Binary file
<script>
WebAssembly.instantiateStreaming(fetch('./pkg/wasm_with_rust_bg.wasm'))
.then((results) => {
const calc = results.instance.exports.calc
console.log(calc(10, 2, 43)) // 12
console.log(calc(10, 2, 45)) // 8
console.log(calc(10, 2, 42)) // 20
console.log(calc(10, 2, 47)) // 5
})
</script>
In this, we are loading the wasm binary file directly which is compiled from wasm-pack. We are loading using fetch and instantiate using instantiateStreaming() method of WebAssembly class available globally. Here we are passing the same parameter as above but as you can see 3rd parameter is numeric. That is ASCII value of operator respectively. Here we can not pass string value because are not passing it to JavaScript so it can further convert to Machine Code, but Passing it to directly WASM file.
You can see the WASM Machine code that looks like below.
Source Repo & Local Setup
git clone https://github.com/piyush-multiplexer/wasm-with-rust.git
cd wasm-with-rust
cargo install wasm-pack
wasm-pack build --target web
serve .
Resources on WebAssembly
Advantages
- The Memory of WebAssembly is Linear. It is an expandable continuous buffer of unsigned bytes that can be read and stored from JavaSCript and WASM synchronously.
- Can import JavaScript functions and export custom function logic specified in WASM.
- Compiled to Binary file, Assembly-like language which gives native speed and performance in the browser.
- and many more based on requirements.
Conclusion
In this tutorial, you get the basic idea of what is WebAssebly or wasm is, its purpose, make wasm binary from Rust Language and how to use it with JavaScript. I hope you enjoyed it give it a thumb, share, and subscribe. Have any doubts or thoughts share them in the comment section below. I am available here.