Last updated: April 13, 2025
Table of Contents
1. Introduction: Rust Meets the Web
Rust's performance and safety features make it appealing not just for systems programming but also for computationally intensive tasks on the web. WebAssembly (Wasm) provides a way to run code written in languages like Rust, C++, or C# directly in web browsers at near-native speed, alongside JavaScript.
This guide provides a practical introduction to compiling Rust code to WebAssembly using the standard
tooling: wasm-pack
and wasm-bindgen
. We'll create a simple Rust library, expose a
function to JavaScript, compile it to Wasm, and call it from a basic HTML page.
Ensure you have Rust installed. If not, see our Getting Started with Rust guide.
2. What is WebAssembly? (Recap)
As covered in our Introduction to WebAssembly, Wasm is a binary instruction format designed as a portable compilation target for high-level languages. It allows running code at high speed within the browser's secure sandbox. Critically, Wasm is designed to interoperate with JavaScript, enabling developers to use Rust (or other languages) for performance-critical modules within a larger web application.
3. The Rust Wasm Toolchain: wasm-pack
and wasm-bindgen
The primary tools for Rust and Wasm development are:
wasm-pack
: A command-line tool for building, testing, and publishing Rust-generated WebAssembly crates. It orchestrates the build process and produces packages suitable for JavaScript consumption (e.g., npm packages).wasm-bindgen
: A Rust library and associated CLI tool that facilitates high-level interaction between Wasm modules and JavaScript. It understands Rust types (like strings, structs) and JavaScript types (like JS objects, DOM manipulation) and generates the necessary glue code for communication.
wasm-pack
uses wasm-bindgen
under the hood.
4. Project Setup
4.1 Installing wasm-pack
Install wasm-pack
using Cargo:
cargo install wasm-pack
Verify the installation:
wasm-pack --version
4.2 Creating a Rust Library Project
We need to create a Rust library (lib
) project, not a binary (bin
) project, as we
intend to call its functions from JavaScript.
cargo new rust_wasm_lib --lib
cd rust_wasm_lib
5. Writing Rust Code for Wasm
5.1 Adding wasm-bindgen
Dependency
First, configure Cargo.toml
to produce a specific type of library suitable for Wasm and add
wasm-bindgen
as a dependency:
[package]
name = "rust_wasm_lib"
version = "0.1.0"
edition = "2021"
# Add these lines:
[lib]
crate-type = ["cdylib", "rlib"] # Compile to dynamic system library and Rust library format
[dependencies]
# Add wasm-bindgen dependency
wasm-bindgen = "0.2"
crate-type = ["cdylib", "rlib"]
: Tells Rust to compile our library into a format suitable for dynamic linking (cdylib
, needed for Wasm) and also as a standard Rust library (rlib
, useful for testing/integration).wasm-bindgen = "0.2"
: Adds the necessary library dependency. Check crates.io for the latest version.
You can also add the dependency via the command line:
cargo add wasm-bindgen
5.2 Exposing a Function to JavaScript
Now, let's write a simple function in src/lib.rs
and expose it to JavaScript using the
#[wasm_bindgen]
attribute.
Replace the contents of src/lib.rs
with:
use wasm_bindgen::prelude::*;
// This attribute marks the function for wasm-bindgen processing
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello from Rust, {}!", name)
}
// Example of another simple function
#[wasm_bindgen]
pub fn add(a: u32, b: u32) -> u32 {
a + b
}
// You can also expose structs or complex logic,
// but let's keep it simple for getting started.
use wasm_bindgen::prelude::*;
: Imports necessary items from the library.#[wasm_bindgen]
: This attribute tellswasm-bindgen
to process the following item (in this case, thegreet
andadd
functions) and generate the necessary JavaScript glue code to make them callable from JS.pub fn ...
: Functions exposed to Wasm must be public (pub
).- Note that we're using Rust's
&str
(string slice) andString
types.wasm-bindgen
handles the conversion to/from JavaScript strings automatically. Similarly, it handles numeric types likeu32
.
6. Building the WebAssembly Package
Now, use wasm-pack
to build the project. We'll target the browser environment.
wasm-pack build --target web
This command will:
- Compile your Rust code to WebAssembly (
.wasm
). - Run
wasm-bindgen
to generate the JavaScript glue code needed to load and run the Wasm module. - Create a
pkg
directory containing the generated.wasm
file, the JavaScript glue code (e.g.,rust_wasm_lib.js
), a TypeScript definition file (.d.ts
), and apackage.json
.
The output in the pkg
directory is ready to be used by JavaScript bundlers (like Webpack,
Rollup, Vite) or directly in a simple HTML setup.
7. Using the Wasm Module in JavaScript
Let's create a simple HTML file to load and use our Wasm module.
7.1 Simple HTML Setup
- Create an
index.html
file in the root of yourrust_wasm_lib
project (outside thesrc
andpkg
directories). - Create a simple JavaScript file, e.g.,
index.js
, in the same directory.
Add the following to index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rust Wasm Example</title>
</head>
<body>
<h1>Rust + WebAssembly Test</h1>
<p>Check the console for output.</p>
<p id="result"></p>
<!-- Important: Use type="module" for ES module imports -->
<script type="module" src="./index.js"></script>
</body>
</html>
7.2 JavaScript Integration
Add the following to index.js
:
// Import the generated JS module and the greet function from the pkg directory
// Note: Adjust the path if your HTML/JS are not in the project root.
import init, { greet, add } from './pkg/rust_wasm_lib.js';
async function run() {
// Initialize the Wasm module. This is required before calling exported functions.
// init() returns a promise that resolves when the Wasm is loaded.
await init();
// Call the exported Rust function
const message = greet("WebAssembly");
console.log(message); // Output: Hello from Rust, WebAssembly!
const sum = add(5, 7);
console.log(`5 + 7 = ${sum}`); // Output: 5 + 7 = 12
// Display result in the HTML
const resultElement = document.getElementById('result');
if (resultElement) {
resultElement.textContent = `Rust says: "${message}" and 5 + 7 = ${sum}`;
}
}
run();
This script imports the default export (the initialization function init
) and our named exports
(greet
, add
) from the JavaScript glue file generated by wasm-pack
. It
calls init()
to load the .wasm
file and then calls the exported functions.
7.3 Running a Simple Server
WebAssembly modules usually need to be served over HTTP(S) due to browser security policies (fetching
.wasm
files via file://
often doesn't work reliably).
You can use any simple HTTP server. If you have Python installed:
# Python 3
python -m http.server
Or use a Node.js server like http-server
:
npm install -g http-server
http-server .
Open your browser to the address provided by the server (e.g., http://localhost:8000
or
http://localhost:8080
). Open the browser's developer console, and you should see the output from
the Rust functions!
8. Conclusion and Next Steps
You've successfully compiled Rust code to WebAssembly and called it from JavaScript! This demonstrates the
basic workflow using wasm-pack
and wasm-bindgen
.
Rust's potential in the web ecosystem via WebAssembly is significant, allowing developers to leverage its performance and safety for complex client-side computations, game development, data processing, and sharing code between server and client.
Further exploration could involve:
- Passing more complex data structures (structs, vectors) between Rust and JS.
- Interacting with JavaScript APIs (like DOM manipulation) from Rust.
- Integrating Rust Wasm modules into modern frontend frameworks (React, Vue, Angular).
- Optimizing Wasm bundle size and performance.