1. rust
  2. /web
  3. /webassembly

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.