Course contentsShow
← Rust
Lesson 1835 of 1,91016. Specialized Runtime TargetsFree lesson

Managing Wasm Memory Buffers

Implement zero-copy serialization and deserialization patterns for data exchange between Rust and JavaScript.

In this lesson, you will learn how to bypass expensive serialization overhead by mapping Rust data structures directly onto Wasm memory buffers for zero-copy exchange with JavaScript.

The Problem with Serialization

When passing data between Rust and JavaScript, the common pattern is to serialize Rust structs into JSON strings or Uint8Array buffers and deserialize them on the other side. While safe, this is computationally expensive—it involves memory allocation and multiple passes over the data. In high-performance Wasm applications, we want to treat the Wasm linear memory as a shared, pre-allocated workspace.

Zero-Copy via Memory Mapping

Zero-copy serialization works by ensuring your Rust struct has a strictly defined #[repr(C)] memory layout. Because Wasm linear memory is just a large ArrayBuffer accessible by both host and guest, we can write a struct to a specific memory offset in Rust and then read those same bytes directly via a JavaScript DataView or TypedArray.

Consider this shared data structure:

#[repr(C)]
#[derive(Copy, Clone)]
pub struct Point {
    pub x: f32,
    pub y: f32,
}

#[no_mangle]
pub fn get_point_ptr() -> *const Point {
    static mut DATA: Point = Point { x: 10.0, y: 20.0 };
    unsafe { &DATA }
}

By exporting the pointer (get_point_ptr), JavaScript can perform a "zero-copy" read without any conversion logic. In JavaScript, you access the WebAssembly.Memory buffer:

// JS: Read directly from the Wasm buffer
const memory = wasmInstance.exports.memory.buffer;
const ptr = wasmInstance.exports.get_point_ptr();
const floatView = new Float32Array(memory, ptr, 2);

console.log(floatView[0], floatView[1]); // 10, 20

Critical Constraints

For this to remain safe, you must ensure:

  1. Memory Alignment: Your structs must follow repr(C) rules to avoid compiler-induced reordering or padding gaps that differ between environments.
  2. Bounds Safety: Since you are accessing raw memory, JavaScript must have enough context (or a length prefix) to avoid reading past the intended buffer, which could leak sensitive data or trigger an out-of-bounds access.
  3. Immutability/Ownership: Ensure that if Rust modifies the struct, JavaScript doesn't read it during a partial write, or vice-versa.

Key Takeaway: By using #[repr(C)] and exposing raw pointers to the Wasm linear memory, you can treat shared data as a memory-mapped interface, eliminating the CPU cost of serializing and deserializing between the host and guest.