WebAssembly (WASM)
WebAssembly (WASM) enables running Rust code in web browsers at near-native performance. Rust has excellent WebAssembly support, making it ideal for performance-critical web applications, games, and computational tasks.
WebAssembly Fundamentals
What is WebAssembly?
WebAssembly is a binary instruction format that runs in web browsers alongside JavaScript. It provides:
- Near-native performance: Compiled code runs much faster than JavaScript
- Language agnostic: Multiple languages can compile to WASM
- Secure: Runs in a sandboxed environment
- Portable: Works across different platforms and browsers
Setting Up Rust for WebAssembly
# Install wasm-pack (the main tool for Rust + WASM)
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# Or using cargo
cargo install wasm-pack
# Install cargo-generate for project templates
cargo install cargo-generate
# Create a new WASM project from template
cargo generate --git https://github.com/rustwasm/wasm-pack-template
Basic Cargo.toml
configuration:
[package]
name = "my-wasm-project"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = "0.3"
# Optional: for console logging
console_error_panic_hook = { version = "0.1", optional = true }
wee_alloc = { version = "0.4", optional = true }
[dependencies.web-sys]
version = "0.3"
features = [
"console",
"Document",
"Element",
"HtmlElement",
"Window",
]
[features]
default = ["console_error_panic_hook"]
Basic WebAssembly with wasm-bindgen
Simple Functions
use wasm_bindgen::prelude::*;
// Import the `console.log` function from the `console` module
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
// Define a macro for easier console logging
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
// Export a function to JavaScript
#[wasm_bindgen]
pub fn greet(name: &str) {
console_log!("Hello, {}!", name);
}
// Simple mathematical operations
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[wasm_bindgen]
pub fn factorial(n: u32) -> u64 {
match n {
0 | 1 => 1,
_ => n as u64 * factorial(n - 1),
}
}
// Working with strings
#[wasm_bindgen]
pub fn reverse_string(input: &str) -> String {
input.chars().rev().collect()
}
// Working with arrays/vectors
#[wasm_bindgen]
pub fn sum_array(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
// Return multiple values using a struct
#[wasm_bindgen]
pub struct MathResult {
sum: i32,
product: i32,
average: f64,
}
#[wasm_bindgen]
impl MathResult {
#[wasm_bindgen(getter)]
pub fn sum(&self) -> i32 {
self.sum
}
#[wasm_bindgen(getter)]
pub fn product(&self) -> i32 {
self.product
}
#[wasm_bindgen(getter)]
pub fn average(&self) -> f64 {
self.average
}
}
#[wasm_bindgen]
pub fn calculate_stats(numbers: &[i32]) -> MathResult {
let sum: i32 = numbers.iter().sum();
let product: i32 = numbers.iter().product();
let average = sum as f64 / numbers.len() as f64;
MathResult { sum, product, average }
}
Building and Using in JavaScript
# Build the WebAssembly module
wasm-pack build --target web --out-dir pkg
# For Node.js
wasm-pack build --target nodejs --out-dir pkg
# For bundlers (webpack, etc.)
wasm-pack build --target bundler --out-dir pkg
Using in HTML/JavaScript:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Rust WebAssembly Demo</title>
</head>
<body>
<script type="module">
import init, {
greet,
add,
factorial,
reverse_string,
sum_array,
calculate_stats
} from './pkg/my_wasm_project.js';
async function run() {
// Initialize the WebAssembly module
await init();
// Call Rust functions
greet('WebAssembly');
console.log('2 + 3 =', add(2, 3));
console.log('5! =', factorial(5));
console.log('Reversed:', reverse_string('Hello World'));
console.log('Sum:', sum_array(new Int32Array([1, 2, 3, 4, 5])));
// Use complex return types
const stats = calculate_stats(new Int32Array([10, 20, 30, 40, 50]));
console.log('Sum:', stats.sum);
console.log('Product:', stats.product);
console.log('Average:', stats.average);
}
run();
</script>
</body>
</html>
Advanced WebAssembly Patterns
Working with JavaScript Objects
use wasm_bindgen::prelude::*;
use js_sys::*;
use web_sys::*;
// Working with JavaScript objects
#[wasm_bindgen]
pub fn process_js_object(obj: &JsValue) -> Result<String, JsValue> {
let obj = obj.dyn_into::<Object>()?;
let keys = Object::keys(&obj);
let mut result = String::new();
for i in 0..keys.length() {
let key = keys.get(i);
let value = Reflect::get(&obj, &key)?;
result.push_str(&format!("{}: {}\n",
key.as_string().unwrap_or_default(),
value.as_string().unwrap_or_default()
));
}
Ok(result)
}
// Working with arrays
#[wasm_bindgen]
pub fn process_js_array(arr: &Array) -> Vec<f64> {
let mut result = Vec::new();
for i in 0..arr.length() {
if let Some(val) = arr.get(i).as_f64() {
result.push(val * 2.0); // Double each value
}
}
result
}
// Creating JavaScript objects from Rust
#[wasm_bindgen]
pub fn create_user_object(name: &str, age: u32, email: &str) -> Result<Object, JsValue> {
let obj = Object::new();
Reflect::set(&obj, &"name".into(), &name.into())?;
Reflect::set(&obj, &"age".into(), &age.into())?;
Reflect::set(&obj, &"email".into(), &email.into())?;
Reflect::set(&obj, &"created_at".into(), &Date::new_0().into())?;
Ok(obj)
}
// Working with Promises
#[wasm_bindgen]
pub async fn async_computation(n: u32) -> Result<u32, JsValue> {
// Simulate async work
let promise = Promise::new(&mut |resolve, _reject| {
let timeout = Closure::wrap(Box::new(move || {
let result = (0..n).sum::<u32>();
resolve.call1(&JsValue::null(), &JsValue::from(result)).unwrap();
}) as Box<dyn FnMut()>);
web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(
timeout.as_ref().unchecked_ref(),
100,
)
.unwrap();
timeout.forget();
});
let result = wasm_bindgen_futures::JsFuture::from(promise).await?;
Ok(result.as_f64().unwrap() as u32)
}
DOM Manipulation
use web_sys::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct DomManager {
document: Document,
container: Element,
}
#[wasm_bindgen]
impl DomManager {
#[wasm_bindgen(constructor)]
pub fn new(container_id: &str) -> Result<DomManager, JsValue> {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let container = document
.get_element_by_id(container_id)
.ok_or("Container not found")?;
Ok(DomManager { document, container })
}
#[wasm_bindgen]
pub fn create_button(&self, text: &str, id: &str) -> Result<(), JsValue> {
let button = self.document
.create_element("button")?
.dyn_into::<HtmlButtonElement>()?;
button.set_inner_text(text);
button.set_id(id);
// Add click event listener
let closure = Closure::wrap(Box::new(move || {
console_log!("Button {} clicked!", id);
}) as Box<dyn Fn()>);
button.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref())?;
closure.forget(); // Important: prevent closure from being dropped
self.container.append_child(&button)?;
Ok(())
}
#[wasm_bindgen]
pub fn create_list(&self, items: Vec<String>) -> Result<(), JsValue> {
let ul = self.document.create_element("ul")?;
for item in items {
let li = self.document.create_element("li")?;
li.set_text_content(Some(&item));
ul.append_child(&li)?;
}
self.container.append_child(&ul)?;
Ok(())
}
#[wasm_bindgen]
pub fn clear(&self) -> Result<(), JsValue> {
self.container.set_inner_html("");
Ok(())
}
#[wasm_bindgen]
pub fn update_style(&self, selector: &str, property: &str, value: &str) -> Result<(), JsValue> {
if let Some(element) = self.document.query_selector(selector)? {
let style = element.dyn_into::<HtmlElement>()?.style();
style.set_property(property, value)?;
}
Ok(())
}
}
// Canvas manipulation example
#[wasm_bindgen]
pub struct CanvasRenderer {
canvas: HtmlCanvasElement,
context: CanvasRenderingContext2d,
}
#[wasm_bindgen]
impl CanvasRenderer {
#[wasm_bindgen(constructor)]
pub fn new(canvas_id: &str) -> Result<CanvasRenderer, JsValue> {
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document
.get_element_by_id(canvas_id)
.ok_or("Canvas not found")?
.dyn_into::<HtmlCanvasElement>()?;
let context = canvas
.get_context("2d")?
.ok_or("Failed to get 2d context")?
.dyn_into::<CanvasRenderingContext2d>()?;
Ok(CanvasRenderer { canvas, context })
}
#[wasm_bindgen]
pub fn draw_circle(&self, x: f64, y: f64, radius: f64, color: &str) {
self.context.begin_path();
self.context.arc(x, y, radius, 0.0, 2.0 * std::f64::consts::PI).unwrap();
self.context.set_fill_style(&color.into());
self.context.fill();
}
#[wasm_bindgen]
pub fn draw_line(&self, x1: f64, y1: f64, x2: f64, y2: f64, color: &str, width: f64) {
self.context.begin_path();
self.context.move_to(x1, y1);
self.context.line_to(x2, y2);
self.context.set_stroke_style(&color.into());
self.context.set_line_width(width);
self.context.stroke();
}
#[wasm_bindgen]
pub fn clear(&self) {
let width = self.canvas.width() as f64;
let height = self.canvas.height() as f64;
self.context.clear_rect(0.0, 0.0, width, height);
}
#[wasm_bindgen]
pub fn animate_particles(&self, count: u32) {
// This would typically be called from JavaScript with requestAnimationFrame
for _ in 0..count {
let x = js_sys::Math::random() * self.canvas.width() as f64;
let y = js_sys::Math::random() * self.canvas.height() as f64;
let radius = js_sys::Math::random() * 5.0 + 1.0;
let hue = js_sys::Math::random() * 360.0;
let color = format!("hsl({}, 100%, 50%)", hue);
self.draw_circle(x, y, radius, &color);
}
}
}
Performance Optimization
Memory Management
use wasm_bindgen::prelude::*;
// Use wee_alloc for smaller memory footprint
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
// Enable console error panic hook for debugging
#[cfg(feature = "console_error_panic_hook")]
pub fn set_panic_hook() {
console_error_panic_hook::set_once();
}
// Efficient data processing with zero-copy when possible
#[wasm_bindgen]
pub struct ImageProcessor {
width: u32,
height: u32,
data: Vec<u8>,
}
#[wasm_bindgen]
impl ImageProcessor {
#[wasm_bindgen(constructor)]
pub fn new(width: u32, height: u32) -> ImageProcessor {
let data = vec![0; (width * height * 4) as usize]; // RGBA
ImageProcessor { width, height, data }
}
#[wasm_bindgen(getter)]
pub fn data_ptr(&self) -> *const u8 {
self.data.as_ptr()
}
#[wasm_bindgen(getter)]
pub fn data_len(&self) -> usize {
self.data.len()
}
#[wasm_bindgen]
pub fn apply_grayscale(&mut self) {
for chunk in self.data.chunks_exact_mut(4) {
let r = chunk[0] as f32;
let g = chunk[1] as f32;
let b = chunk[2] as f32;
// Luminance formula
let gray = (0.299 * r + 0.587 * g + 0.114 * b) as u8;
chunk[0] = gray;
chunk[1] = gray;
chunk[2] = gray;
// Alpha channel (chunk[3]) remains unchanged
}
}
#[wasm_bindgen]
pub fn apply_blur(&mut self, radius: f32) {
// Simple box blur implementation
let width = self.width as usize;
let height = self.height as usize;
let r = radius as usize;
let mut temp = self.data.clone();
// Horizontal pass
for y in 0..height {
for x in 0..width {
let mut r_sum = 0u32;
let mut g_sum = 0u32;
let mut b_sum = 0u32;
let mut count = 0u32;
for dx in -(r as isize)..=(r as isize) {
let nx = x as isize + dx;
if nx >= 0 && nx < width as isize {
let idx = (y * width + nx as usize) * 4;
r_sum += self.data[idx] as u32;
g_sum += self.data[idx + 1] as u32;
b_sum += self.data[idx + 2] as u32;
count += 1;
}
}
let idx = (y * width + x) * 4;
temp[idx] = (r_sum / count) as u8;
temp[idx + 1] = (g_sum / count) as u8;
temp[idx + 2] = (b_sum / count) as u8;
}
}
self.data = temp;
}
}
// Efficient numerical computations
#[wasm_bindgen]
pub fn mandelbrot_set(
width: u32,
height: u32,
zoom: f64,
center_x: f64,
center_y: f64,
max_iterations: u32,
) -> Vec<u32> {
let mut result = Vec::with_capacity((width * height) as usize);
for y in 0..height {
for x in 0..width {
let cx = (x as f64 / width as f64 - 0.5) * zoom + center_x;
let cy = (y as f64 / height as f64 - 0.5) * zoom + center_y;
let mut zx = 0.0;
let mut zy = 0.0;
let mut iteration = 0;
while zx * zx + zy * zy < 4.0 && iteration < max_iterations {
let temp = zx * zx - zy * zy + cx;
zy = 2.0 * zx * zy + cy;
zx = temp;
iteration += 1;
}
result.push(iteration);
}
}
result
}
Build Optimization
# Cargo.toml optimizations
[profile.release]
# Enable maximum optimizations
opt-level = 3
# Enable link-time optimization
lto = true
# Reduce binary size
codegen-units = 1
# Abort on panic instead of unwinding
panic = "abort"
[profile.release.package."*"]
# Optimize dependencies
opt-level = 3
# Feature flags for conditional compilation
[features]
default = ["console_error_panic_hook"]
console_error_panic_hook = ["dep:console_error_panic_hook"]
wee_alloc = ["dep:wee_alloc"]
Game Development Example
Simple 2D Game
use wasm_bindgen::prelude::*;
use web_sys::*;
use js_sys::Math;
#[wasm_bindgen]
pub struct Game {
canvas: HtmlCanvasElement,
context: CanvasRenderingContext2d,
player: Player,
enemies: Vec<Enemy>,
bullets: Vec<Bullet>,
score: u32,
game_over: bool,
}
#[derive(Clone)]
struct Player {
x: f64,
y: f64,
width: f64,
height: f64,
speed: f64,
}
#[derive(Clone)]
struct Enemy {
x: f64,
y: f64,
width: f64,
height: f64,
speed: f64,
health: u32,
}
#[derive(Clone)]
struct Bullet {
x: f64,
y: f64,
width: f64,
height: f64,
speed: f64,
}
#[wasm_bindgen]
impl Game {
#[wasm_bindgen(constructor)]
pub fn new(canvas_id: &str) -> Result<Game, JsValue> {
let document = window().unwrap().document().unwrap();
let canvas = document
.get_element_by_id(canvas_id)
.ok_or("Canvas not found")?
.dyn_into::<HtmlCanvasElement>()?;
let context = canvas
.get_context("2d")?
.unwrap()
.dyn_into::<CanvasRenderingContext2d>()?;
let player = Player {
x: canvas.width() as f64 / 2.0,
y: canvas.height() as f64 - 50.0,
width: 30.0,
height: 30.0,
speed: 5.0,
};
Ok(Game {
canvas,
context,
player,
enemies: Vec::new(),
bullets: Vec::new(),
score: 0,
game_over: false,
})
}
#[wasm_bindgen]
pub fn update(&mut self) {
if self.game_over {
return;
}
// Spawn enemies randomly
if Math::random() < 0.02 {
self.spawn_enemy();
}
// Update bullets
self.bullets.retain_mut(|bullet| {
bullet.y -= bullet.speed;
bullet.y > 0.0
});
// Update enemies
self.enemies.retain_mut(|enemy| {
enemy.y += enemy.speed;
if enemy.y > self.canvas.height() as f64 {
false
} else {
true
}
});
// Check collisions
self.check_collisions();
// Check if player collides with enemies
for enemy in &self.enemies {
if self.check_collision(&self.player, enemy) {
self.game_over = true;
break;
}
}
}
#[wasm_bindgen]
pub fn render(&self) {
// Clear canvas
self.context.clear_rect(0.0, 0.0,
self.canvas.width() as f64,
self.canvas.height() as f64);
if self.game_over {
self.render_game_over();
return;
}
// Draw player
self.context.set_fill_style(&"blue".into());
self.context.fill_rect(self.player.x, self.player.y,
self.player.width, self.player.height);
// Draw enemies
self.context.set_fill_style(&"red".into());
for enemy in &self.enemies {
self.context.fill_rect(enemy.x, enemy.y, enemy.width, enemy.height);
}
// Draw bullets
self.context.set_fill_style(&"yellow".into());
for bullet in &self.bullets {
self.context.fill_rect(bullet.x, bullet.y, bullet.width, bullet.height);
}
// Draw score
self.context.set_fill_style(&"white".into());
self.context.set_font("20px Arial");
self.context.fill_text(&format!("Score: {}", self.score), 10.0, 30.0).unwrap();
}
#[wasm_bindgen]
pub fn move_player(&mut self, direction: &str) {
if self.game_over {
return;
}
match direction {
"left" => {
self.player.x = (self.player.x - self.player.speed).max(0.0);
}
"right" => {
let max_x = self.canvas.width() as f64 - self.player.width;
self.player.x = (self.player.x + self.player.speed).min(max_x);
}
"up" => {
self.player.y = (self.player.y - self.player.speed).max(0.0);
}
"down" => {
let max_y = self.canvas.height() as f64 - self.player.height;
self.player.y = (self.player.y + self.player.speed).min(max_y);
}
_ => {}
}
}
#[wasm_bindgen]
pub fn shoot(&mut self) {
if self.game_over {
return;
}
let bullet = Bullet {
x: self.player.x + self.player.width / 2.0 - 2.5,
y: self.player.y,
width: 5.0,
height: 10.0,
speed: 7.0,
};
self.bullets.push(bullet);
}
#[wasm_bindgen]
pub fn reset(&mut self) {
self.enemies.clear();
self.bullets.clear();
self.score = 0;
self.game_over = false;
self.player.x = self.canvas.width() as f64 / 2.0;
self.player.y = self.canvas.height() as f64 - 50.0;
}
#[wasm_bindgen(getter)]
pub fn score(&self) -> u32 {
self.score
}
#[wasm_bindgen(getter)]
pub fn game_over(&self) -> bool {
self.game_over
}
fn spawn_enemy(&mut self) {
let enemy = Enemy {
x: Math::random() * (self.canvas.width() as f64 - 30.0),
y: 0.0,
width: 30.0,
height: 30.0,
speed: 1.0 + Math::random() * 2.0,
health: 1,
};
self.enemies.push(enemy);
}
fn check_collisions(&mut self) {
let mut bullets_to_remove = Vec::new();
let mut enemies_to_remove = Vec::new();
for (bullet_idx, bullet) in self.bullets.iter().enumerate() {
for (enemy_idx, enemy) in self.enemies.iter().enumerate() {
if self.check_collision(bullet, enemy) {
bullets_to_remove.push(bullet_idx);
enemies_to_remove.push(enemy_idx);
self.score += 10;
}
}
}
// Remove in reverse order to maintain indices
bullets_to_remove.sort_unstable();
enemies_to_remove.sort_unstable();
for &idx in bullets_to_remove.iter().rev() {
self.bullets.remove(idx);
}
for &idx in enemies_to_remove.iter().rev() {
self.enemies.remove(idx);
}
}
fn check_collision<T, U>(&self, obj1: &T, obj2: &U) -> bool
where
T: HasBounds,
U: HasBounds,
{
let (x1, y1, w1, h1) = obj1.bounds();
let (x2, y2, w2, h2) = obj2.bounds();
x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2
}
fn render_game_over(&self) {
self.context.set_fill_style(&"rgba(0, 0, 0, 0.8)".into());
self.context.fill_rect(0.0, 0.0,
self.canvas.width() as f64,
self.canvas.height() as f64);
self.context.set_fill_style(&"white".into());
self.context.set_font("48px Arial");
self.context.set_text_align("center");
self.context.fill_text("GAME OVER",
self.canvas.width() as f64 / 2.0,
self.canvas.height() as f64 / 2.0 - 50.0).unwrap();
self.context.set_font("24px Arial");
self.context.fill_text(&format!("Final Score: {}", self.score),
self.canvas.width() as f64 / 2.0,
self.canvas.height() as f64 / 2.0).unwrap();
self.context.fill_text("Press R to restart",
self.canvas.width() as f64 / 2.0,
self.canvas.height() as f64 / 2.0 + 50.0).unwrap();
}
}
trait HasBounds {
fn bounds(&self) -> (f64, f64, f64, f64);
}
impl HasBounds for Player {
fn bounds(&self) -> (f64, f64, f64, f64) {
(self.x, self.y, self.width, self.height)
}
}
impl HasBounds for Enemy {
fn bounds(&self) -> (f64, f64, f64, f64) {
(self.x, self.y, self.width, self.height)
}
}
impl HasBounds for Bullet {
fn bounds(&self) -> (f64, f64, f64, f64) {
(self.x, self.y, self.width, self.height)
}
}
Integration with JavaScript Frameworks
React Integration
// React component using WebAssembly
import React, { useEffect, useRef, useState } from 'react';
import init, { Game } from './pkg/wasm_game';
const GameComponent: React.FC = () => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const gameRef = useRef<Game | null>(null);
const [score, setScore] = useState(0);
const [gameOver, setGameOver] = useState(false);
useEffect(() => {
const initGame = async () => {
await init();
if (canvasRef.current) {
canvasRef.current.id = 'game-canvas';
gameRef.current = new Game('game-canvas');
}
};
initGame();
// Game loop
const gameLoop = () => {
if (gameRef.current) {
gameRef.current.update();
gameRef.current.render();
setScore(gameRef.current.score);
setGameOver(gameRef.current.game_over);
}
requestAnimationFrame(gameLoop);
};
requestAnimationFrame(gameLoop);
// Keyboard event handlers
const handleKeyDown = (event: KeyboardEvent) => {
if (!gameRef.current) return;
switch (event.key) {
case 'ArrowLeft':
gameRef.current.move_player('left');
break;
case 'ArrowRight':
gameRef.current.move_player('right');
break;
case 'ArrowUp':
gameRef.current.move_player('up');
break;
case 'ArrowDown':
gameRef.current.move_player('down');
break;
case ' ':
event.preventDefault();
gameRef.current.shoot();
break;
case 'r':
case 'R':
if (gameRef.current.game_over) {
gameRef.current.reset();
}
break;
}
};
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, []);
return (
<div className="game-container">
<div className="game-ui">
<h2>Space Shooter</h2>
<p>Score: {score}</p>
{gameOver && <p>Game Over! Press R to restart</p>}
<p>Use arrow keys to move, spacebar to shoot</p>
</div>
<canvas
ref={canvasRef}
width={800}
height={600}
style={{ border: '1px solid black', display: 'block' }}
/>
</div>
);
};
export default GameComponent;
Best Practices and Tips
1. Memory Management
// Avoid unnecessary allocations
#[wasm_bindgen]
pub fn efficient_string_processing(input: &str) -> String {
// Process in-place when possible
input.chars()
.filter(|c| c.is_alphanumeric())
.collect()
}
// Use static data when possible
const LOOKUP_TABLE: [u8; 256] = [0; 256]; // Initialize at compile time
// Pool objects to reduce GC pressure
thread_local! {
static STRING_POOL: RefCell<Vec<String>> = RefCell::new(Vec::new());
}
#[wasm_bindgen]
pub fn get_pooled_string() -> String {
STRING_POOL.with(|pool| {
pool.borrow_mut().pop().unwrap_or_else(|| String::new())
})
}
#[wasm_bindgen]
pub fn return_to_pool(mut s: String) {
s.clear();
STRING_POOL.with(|pool| {
if pool.borrow().len() < 100 {
pool.borrow_mut().push(s);
}
});
}
2. Error Handling
use wasm_bindgen::prelude::*;
// Use Result types for fallible operations
#[wasm_bindgen]
pub fn safe_divide(a: f64, b: f64) -> Result<f64, JsValue> {
if b == 0.0 {
Err(JsValue::from_str("Division by zero"))
} else {
Ok(a / b)
}
}
// Custom error types
#[wasm_bindgen]
pub struct MathError {
message: String,
}
#[wasm_bindgen]
impl MathError {
#[wasm_bindgen(getter)]
pub fn message(&self) -> String {
self.message.clone()
}
}
impl From<MathError> for JsValue {
fn from(error: MathError) -> Self {
JsValue::from_str(&error.message)
}
}
3. Testing WebAssembly
#[cfg(test)]
mod tests {
use super::*;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[wasm_bindgen_test]
fn test_factorial() {
assert_eq!(factorial(5), 120);
assert_eq!(factorial(0), 1);
}
#[wasm_bindgen_test]
async fn test_async_computation() {
let result = async_computation(10).await.unwrap();
assert_eq!(result, 45); // Sum of 0..10
}
}
4. Debugging
// Enable debug logging
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
#[wasm_bindgen(js_namespace = console)]
fn error(s: &str);
}
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
macro_rules! console_error {
($($t:tt)*) => (error(&format_args!($($t)*).to_string()))
}
// Debug assertions
#[wasm_bindgen]
pub fn debug_function(value: i32) -> i32 {
debug_assert!(value >= 0, "Value must be non-negative");
console_log!("Processing value: {}", value);
value * 2
}
WebAssembly with Rust provides a powerful way to bring high-performance code to the web. The combination of Rust's safety guarantees and WebAssembly's performance makes it ideal for computationally intensive web applications, games, and tools that require near-native performance in the browser.