Rust compiles to WebAssembly at near-native speed with zero garbage
collection pauses — ideal for physics simulations that must maintain 60
fps under heavy CPU load. wasm-bindgen makes interop with
JavaScript trivial: expose Rust functions and share typed-array memory
directly without copies.
1Set up the Rust / wasm-pack project
# 1. Install Rust (if not present) curl --proto '=https' --tlsv1.2
-sSf https://sh.rustup.rs | sh # 2. Add the WASM target rustup target
add wasm32-unknown-unknown # 3. Install wasm-pack cargo install
wasm-pack # 4. Create a new library crate cargo new --lib particle-sim
cd particle-sim
# Cargo.toml — add these dependencies and crate type [package] name =
"particle-sim" version = "0.1.0" edition = "2021" [lib] crate-type =
["cdylib"] [dependencies] wasm-bindgen = "0.2" [profile.release]
opt-level = 3 lto = true
// Continued in src/lib.rs — inside impl Particles: /// Returns a
pointer to the position X buffer. /// The JS side can create a
Float32Array view without copying. pub fn px_ptr(&self) -> *const
f32 { self.sim.px.as_ptr() } pub fn py_ptr(&self) -> *const f32 {
self.sim.py.as_ptr() } pub fn len(&self) -> usize { N } }
# Build the WASM package wasm-pack build --target web --release #
Output: pkg/particle_sim.js (JS bindings) + pkg/particle_sim_bg.wasm
4Load and call from JavaScript
import init, { Particles } from './pkg/particle_sim.js'; const wasm =
await init('./pkg/particle_sim_bg.wasm'); const sim = new Particles();
const N = sim.len(); // Get zero-copy views into WASM linear memory //
These point directly into the WASM heap — no copy! const memory =
wasm.memory; function getPxView() { return new
Float32Array(memory.buffer, sim.px_ptr(), N); } function getPyView() {
return new Float32Array(memory.buffer, sim.py_ptr(), N); } // Advance
the simulation at 60 fps function update() { sim.step(1 / 60); }
Zero-copy access:memory.buffer is the
WASM linear memory ArrayBuffer. Creating a typed array
view from a pointer avoids any serialisation — the JS side reads the
bytes the Rust side wrote directly.
5Zero-copy rendering with Three.js
import * as THREE from
'https://cdn.jsdelivr.net/npm/three@0.160/build/three.module.js'; //
Build the geometry from current buffer view const geo = new
THREE.BufferGeometry(); // interleave x,y into a 3-component position
array const pos3 = new Float32Array(N * 3); let posAttr = new
THREE.BufferAttribute(pos3, 3);
posAttr.setUsage(THREE.DynamicDrawUsage); geo.setAttribute('position',
posAttr); const mat = new THREE.PointsMaterial({ size: 0.002, color:
0x22c55e }); const points = new THREE.Points(geo, mat);
scene.add(points); renderer.setAnimationLoop(() => { sim.step(1 / 60);
// After step(), refresh the typed-array view (WASM memory may have
grown) const px = new Float32Array(memory.buffer, sim.px_ptr(), N);
const py = new Float32Array(memory.buffer, sim.py_ptr(), N); // Copy
into interleaved position buffer for (let i = 0; i < N; i++) {
pos3[i * 3] = px[i]; pos3[i * 3 + 1] = py[i]; pos3[i * 3 + 2] = 0; }
posAttr.needsUpdate = true; renderer.render(scene, camera); });
The copy from (px, py) to interleaved pos3 is unavoidable without
changing the Rust data layout to SoA→AoS. For maximum performance
restructure the Rust struct to store xyz interleaved
directly.
Frequently Asked Questions
What will I learn in this tutorial?
Compile a particle physics simulation written in Rust to WebAssembly, call it from JavaScript, and render 100k particles at 60fps in the browser.
What topics are covered in this tutorial?
This tutorial covers: Set up the Rust/wasm-pack project, Write the particle simulation in Rust, Expose typed-array buffers to JS, Load and call from JavaScript, Zero-copy rendering with Three.js.
What tools and technologies does this tutorial use?
This tutorial uses Rust, WebAssembly, wasm-bindgen, Three.js.
How long does this tutorial take?
This tutorial takes approximately 70 minutes to complete.
What prerequisites do I need before starting?
This is a Advanced-level tutorial — no special preparation beyond basic JavaScript is assumed.