EthicsComputerCourses
Join training +91 90532 10052

Rust & WebAssembly

Rust · Lesson 41 of 43 Advanced 9 min read Updated

WebAssembly (Wasm) is a compact binary instruction format that every modern browser can run at close to native speed, inside the same safe sandbox as JavaScript. It is not a replacement for JavaScript: JavaScript still drives the page, and Wasm takes over jobs that need raw speed or code you already have in another language, such as image editing, PDF generation, games, maths and data validation.

Rust is one of the best languages for Wasm because it needs no garbage collector and no large runtime, so the files stay small. In this lesson you will build a loan EMI calculator in Rust, call it from a web page, run another Rust program outside the browser with WASI, and learn how to shrink the output.

Wasm targets in Rust

Rust produces Wasm through ordinary compilation targets, just like it produces programs for Windows or Linux. The ones you will meet:

TargetUsed forStandard library
wasm32-unknown-unknownBrowsers and JavaScript hosts, usually with wasm-bindgenYes, but no files, clock or threads by itself
wasm32-wasip1Command-line programs on WASI runtimes (preview 1 API)Yes
wasm32-wasip2WASI 0.2 and the Component ModelYes
wasm32v1-noneMinimal Wasm without stdNo (no_std only)

All four are Tier 2 targets, which means the Rust project builds them and ships them through rustup. Install a target once:

Terminal
rustup target add wasm32-unknown-unknown
rustup target add wasm32-wasip2

WASI (WebAssembly System Interface) is a standard set of functions that lets Wasm programs read files, get the time and print text outside a browser, with permissions granted by the runtime.

Building an EMI calculator for the browser

An EMI (equated monthly instalment) is the fixed amount a borrower pays every month. For a loan of P rupees at a monthly interest rate r (annual rate divided by 12 and by 100) over n months, EMI = P x r x (1 + r)^n / ((1 + r)^n - 1).

Step 1: create a library crate

Terminal
cargo new --lib emi-calc
cd emi-calc
cargo add wasm-bindgen

Then tell Cargo what kind of library to produce, in Cargo.toml:

TOML
[lib]
crate-type = ["cdylib", "rlib"]

cdylib produces a standalone .wasm file that other languages can load. rlib is the normal Rust library format; keeping it means cargo test and other Rust crates can still use the code.

Step 2: write and export the functions

Replace src/lib.rs:

Rust
use wasm_bindgen::prelude::*;

/// Monthly instalment for a loan.
#[wasm_bindgen]
pub fn emi(principal: f64, annual_rate: f64, months: u32) -> f64 {
    if months == 0 {
        return 0.0;
    }
    let r = annual_rate / 12.0 / 100.0;
    if r == 0.0 {
        return principal / f64::from(months);
    }
    let growth = (1.0 + r).powi(months as i32);
    principal * r * growth / (growth - 1.0)
}

/// A ready-to-show sentence for the web page.
#[wasm_bindgen]
pub fn emi_summary(principal: f64, annual_rate: f64, months: u32) -> String {
    let monthly = emi(principal, annual_rate, months);
    let interest = monthly * f64::from(months) - principal;
    format!("EMI Rs. {monthly:.2}, total interest Rs. {interest:.2}")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn five_lakh_at_nine_percent_for_five_years() {
        let monthly = emi(500_000.0, 9.0, 60);
        assert!((monthly - 10_379.18).abs() < 0.01);
    }
}

#[wasm_bindgen] marks a function as visible to JavaScript and generates the glue code that converts values. Numbers pass straight through (a JavaScript number is an f64), and String results are copied into a JavaScript string for you. The functions are still plain Rust, so cargo test runs the test on your own computer without any browser.

Step 3: compile to Wasm

Terminal
cargo build --release --target wasm32-unknown-unknown

This writes target/wasm32-unknown-unknown/release/emi_calc.wasm. Notice the underscore: library file names use the crate name, where hyphens become underscores.

Step 4: generate the JavaScript glue

The raw .wasm file only understands numbers. The wasm-bindgen CLI reads it and writes the JavaScript that handles strings and other types:

Terminal
cargo install wasm-bindgen-cli --version 0.2.130
wasm-bindgen --target web --out-dir pkg target/wasm32-unknown-unknown/release/emi_calc.wasm

The CLI version must match the wasm-bindgen version in your Cargo.lock exactly, otherwise it refuses to work; check Cargo.lock and install that version. --target web produces an ES module that a browser can load without a bundler. The pkg folder now holds emi_calc.js, emi_calc_bg.wasm and TypeScript .d.ts type files.

Step 5: call it from a page

Create index.html next to pkg:

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>EMI calculator</title>
</head>
<body>
  <script type="module">
    import init, { emi, emi_summary } from "./pkg/emi_calc.js";

    await init();
    console.log(emi(500000, 9, 60).toFixed(2));
    console.log(emi_summary(500000, 9, 60));
  </script>
</body>
</html>
Output
10379.18
EMI Rs. 10379.18, total interest Rs. 122750.66

The default export init downloads and starts the Wasm module; you must await it before calling any exported function. Browsers do not load modules from file:// addresses, so serve the folder with any local web server, for example python -m http.server 8000, and open http://localhost:8000 with the developer console visible. A loan of Rs. 5,00,000 at 9% for 60 months costs Rs. 10,379.18 a month.

Wasm outside the browser with WASI

The same toolchain builds normal command-line programs for WASI runtimes such as Wasmtime. They run in a sandbox: by default they cannot touch your files or network unless the runtime grants it. That makes Wasm useful for plugins and for running untrusted code on servers.

Rust
// src/main.rs of a binary crate called hello-wasi
fn main() {
    let args: Vec<String> = std::env::args().skip(1).collect();
    let name = args.first().map(String::as_str).unwrap_or("friend");
    println!("Namaste, {name}! This program is running as WebAssembly.");
}
Terminal
cargo build --release --target wasm32-wasip2
wasmtime target/wasm32-wasip2/release/hello-wasi.wasm Kabir
Output
Namaste, Kabir! This program is running as WebAssembly.

Binary file names keep the package name exactly, hyphen included. The .wasm file is portable: the same file runs on Windows, Linux or macOS wherever Wasmtime is installed.

Keeping Wasm files small

Every kilobyte has to be downloaded before the page can use it, so size matters more than in a desktop program. Add a release profile tuned for size:

TOML
[profile.release]
opt-level = "z"     # optimise for size ("s" is a milder option)
lto = true          # optimise across all crates at link time
codegen-units = 1   # slower build, smaller and faster output

Then run wasm-opt -Oz from the Binaryen toolkit on the final .wasm for a further cut. Also watch your dependencies: formatting large numbers with format! and pulling in big crates both add code. Measure with the file size before and after each change.

For whole web applications written in Rust, frameworks such as Leptos, Yew and Dioxus build on wasm-bindgen. For most teams, though, the sweet spot is a small Rust module doing heavy work behind a normal JavaScript or TypeScript front end.

Common mistakes

  • Forgetting crate-type = ["cdylib"]. Without it, cargo build produces no .wasm file you can load.
  • A wasm-bindgen CLI version that does not match the crate. Install the exact version listed in Cargo.lock.
  • Calling exports before await init(). The module is not ready yet, so the call fails.
  • Opening index.html by double-clicking. Modules and Wasm need a web server; use a local one.
  • Expecting Wasm to make everything faster. Crossing between JavaScript and Wasm has a cost; send big chunks of work, not thousands of tiny calls.

Key points

  • WebAssembly is a portable, sandboxed binary format; Rust compiles to it through targets installed with rustup target add.
  • wasm32-unknown-unknown with #[wasm_bindgen] is the browser path; wasm32-wasip1 and wasm32-wasip2 are for WASI runtimes such as Wasmtime.
  • A browser library needs crate-type = ["cdylib", "rlib"], a release build, and the wasm-bindgen CLI at the same version as the crate.
  • With --target web, JavaScript imports the generated module and awaits its default init function before calling exports.
  • Shrink output with opt-level = "z", lto = true and wasm-opt, and keep logic testable as plain Rust.

Exercise

Extend the EMI calculator for Isha's loan comparison page:

  1. Add an exported function total_payable(principal: f64, annual_rate: f64, months: u32) -> f64 that returns EMI times months.
  2. Add a Rust unit test for a Rs. 50,000 loan at 12% for 12 months (EMI about Rs. 4,442.44).
  3. Write the two commands that rebuild the module and regenerate the pkg folder.
  4. Show the JavaScript line that prints the total with two decimals.
Show solution
Rust
#[wasm_bindgen]
pub fn total_payable(principal: f64, annual_rate: f64, months: u32) -> f64 {
    emi(principal, annual_rate, months) * f64::from(months)
}

#[test]
fn fifty_thousand_at_twelve_percent() {
    let monthly = emi(50_000.0, 12.0, 12);
    assert!((monthly - 4_442.44).abs() < 0.01);
    let total = total_payable(50_000.0, 12.0, 12);
    assert!((total - 53_309.27).abs() < 0.01);
}
Terminal
cargo build --release --target wasm32-unknown-unknown
wasm-bindgen --target web --out-dir pkg target/wasm32-unknown-unknown/release/emi_calc.wasm
JavaScript
console.log(total_payable(50000, 12, 12).toFixed(2));
Output
53309.27

Put the test inside the existing tests module. total_payable reuses emi, so both functions always agree. The monthly rate is 1%, the EMI is Rs. 4,442.44 and twelve instalments add up to Rs. 53,309.27, so the interest is Rs. 3,309.27.

Quick Quiz

1. Which target do you use for Rust code that runs in a browser with wasm-bindgen?

2. Why must a browser Wasm library set crate-type = ["cdylib", ...]?

3. What must JavaScript do before calling an exported function from a --target web module?

4. What replaced the wasm32-wasi target name?

Learn Rust with a mentor

Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call