← Back to Lessons Lesson 12 of 28
Intermediate data-structures
State Management
The Challenge
In Rust/Wasm apps, state lives in two places:
- Rust (Wasm linear memory) — game state, data models, computation results
- JavaScript — DOM state, UI state, browser APIs
You need patterns to keep them in sync without excessive copying.
Pattern 1: Rust Owns State (Recommended)
The simplest and most performant pattern — Rust owns all application state, JavaScript only reads it:
#[wasm_bindgen]
pub struct GameState {
score: u32,
level: u32,
entities: Vec<Entity>,
}
#[wasm_bindgen]
impl GameState {
// JS calls methods to mutate state
pub fn update(&mut self, dt: f64) { /* physics */ }
pub fn add_entity(&mut self, x: f64, y: f64) { /* ... */ }
// JS reads state via getters
pub fn score(&self) -> u32 { self.score }
pub fn level(&self) -> u32 { self.level }
pub fn entity_positions(&self) -> Vec<f64> { /* flat array */ }
}const state = new GameState();
function gameLoop() {
state.update(16.67); // mutate in Rust
render(state.entity_positions()); // read from JS
requestAnimationFrame(gameLoop);
}Pattern 2: Message Passing
For complex apps, use a command pattern — JS sends actions, Rust processes them:
#[wasm_bindgen]
pub enum Action {
AddItem,
RemoveItem,
UpdateScore,
Reset,
}
#[wasm_bindgen]
impl AppState {
pub fn dispatch(&mut self, action: Action, payload: &str) -> String {
match action {
Action::AddItem => {
self.items.push(payload.to_string());
format!("Added: {}", payload)
}
Action::RemoveItem => {
// parse index from payload
"Removed".to_string()
}
Action::Reset => {
self.items.clear();
"Reset".to_string()
}
_ => "Unknown action".to_string(),
}
}
}Pattern 3: Shared Memory (Advanced)
For bulk data (game worlds, image buffers), share memory directly:
#[wasm_bindgen]
impl World {
/// Returns a pointer to the pixel buffer in Wasm memory
pub fn pixels_ptr(&self) -> *const u8 {
self.pixels.as_ptr()
}
pub fn pixels_len(&self) -> usize {
self.pixels.len()
}
}// JS reads directly from Wasm memory — zero copy!
const ptr = world.pixels_ptr();
const len = world.pixels_len();
const pixels = new Uint8Array(wasm.memory.buffer, ptr, len);
// Draw to canvas
const imageData = new ImageData(
new Uint8ClampedArray(pixels.buffer, pixels.byteOffset, pixels.byteLength),
width, height
);
ctx.putImageData(imageData, 0, 0);Anti-Patterns to Avoid
| Don't | Why | Do Instead |
|---|---|---|
| Serialize to JSON every frame | Slow, allocates strings | Return flat arrays or use shared memory |
| Clone entire state to JS | Expensive copy | Expose getters for what JS needs |
| Let JS modify Rust memory directly | Unsafe, can corrupt state | Use methods with validation |
| Store DOM references in Rust | Complex lifetime issues | Let JS handle DOM, Rust handles data |
Try It
The starter code shows a state struct with add/remove/query methods. This "Rust owns state" pattern works for most applications.
Try It
Chapter Quiz
Pass all questions to complete this lesson