[WASM] Set up WASM with Rust
※ I'm neither a native speaker nor an expert. I'm just a noob student who wants to develop the literacy in English and software knowledge. If you find any wrong content or awkward expressions, please feel free to let me know. Thank you!
WebAssembly(wasm) is a binary code that can be executed in a web browser directly. It allows us to use programs written with C, C++, Rust, etc. on the web and achieve high performance close to the native programs. I expect this technology will bring a quite considerable advantage to web applications, so I have just got interested and started to explore WASM recently.
How to start
First, a build tool is required that compiles a native code to a WASM file. For Rust, this tool is named 'wasm-pack'. This can be installed by the following command.
cargo install wasm-pack
https://rustwasm.github.io/wasm-pack/installer/
Then, run the following command to create a new project.
wasm-pack new [project-name]
I named the project name 'first-wasm'. After that, you can see the several files created by wasm-pack. The 'lib.rs' file will be in the 'src' folder. The function named 'greet' is generated by default, and let's add a new function, 'add'.
mod utils;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet() {
alert("Hello, first-wasm!");
}
#[wasm_bindgen]
pub fn add(a: i32, b: i32) {
a + b
}
To make a .wasm file, we have to run the command below. There are several options. Since I don't use any bundler at this time, the option '--target web' is provided.
| Option | Description |
| not specified or bundler | This assumes a model where the module itself is natively an ES module. To consume the output, however, we need a bundler(e.g. webpack). |
| nodejs | This is when WebAssembly is deployed into Node.js. We can use the generated JS shims by "require"ing like any other Node module. |
| web | This option will be used when not using a bundler but still running a code in a web browser. |
| no-modules | This is similar to the "web" option. The difference is that it does not support local JS snippets and does not generate an ES module. |
wasm-pack build --target web
// Using this, wasm-pack execute the following commands under the hood.
// rustup target add wasm32-unknown-unknown
// cargo build --target wasm32-unknown-unknown --release
After that, the "pkg" folder is generated, containing the .wasm file, the .ts file for a function declaration, and the .js file as a glue code. If you take a look at the .js file, you can see the module functions that are in src.rs (greet, add) and "__wbg_init". The latter is what we have to call to load a WASM module.
export function greet() {
wasm.greet();
}
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
export function add(a, b) {
const ret = wasm.add(a, b);
return ret;
}
// ...
async function __wbg_init(input) {
// ...
}
export default __wbg_init;
Now, let's use these functions on the web. To see how it works, I made a brief HTML file and a node server. (I have no idea how to represent directories properly.)
node
|--js
| |--pkg
| |--index.js
|--routes
|--|--index.js
|--app.js
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
hello wasm
</title>
</head>
<body>
<div>
Hello
</div>
<script type="module" src="/scripts/index.js"></script>
</body>
</html>
// app.js
const express = require('express');
const path = require('path');
const app = express();
const pageRouter = require('./routes/index.js');
app.set('port', process.env.PORT || 3000);
app.use(express.static(path.join(__dirname, './views')));
app.use('/scripts', express.static(path.join(__dirname, 'js')));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use('/', pageRouter);
app.use('/', (err, req, res, next) =>
{
res.locals.message = err.message;
res.status(err.status || 500);
res.render('error');
});
app.listen(app.get('port'), () => {
console.log("Server listening on port ", app.get('port'));
});
This index.js file is the code actually to use the WASM module. The 'init' function must be followed by any other functions. Otherwise, the WASM might not be loaded successfully, leading to an error.
// js/index.js
import init, {greet, add} from './pkg/first_wasm.js';
// Since the init function is defined as async function,
// we have to use async/await pattern.
const run = async () =>
{
await init();
greet();
console.log(add(1, 2));
}
run();
/* you can write using Promise.then method also.
const run = () =>
{
greet();
console.log(add(1, 2));
}
init().then(run);
Once you run the server and access localhost:3000, The alert message will be activated and the result of the addition will be logged in the console.


That's all! I should take a deeper dive into wasm-bindgen and Rust(I didn't understand how the attribute #[wasm-bindgen] works). Also, I will add the content using bundler(webpack) here.
References
written by Sang Hyeok Park