Inside Mog: How We Built a Spreadsheet Engine in Rust
Why Rust for a Spreadsheet Engine
When we set out to build Mog, the choice of language mattered more than usual. A spreadsheet engine is fundamentally a compute-intensive runtime: it evaluates thousands of formulas, manages complex dependency graphs, and streams rendering data at 60fps. We needed a language that could deliver near-native performance while compiling to every target we cared about.
Rust gave us all three: raw speed, memory safety without a garbage collector, and cross-platform reach. The same Rust codebase compiles to WebAssembly for browsers, N-API native modules for Node.js, and Python extensions via PyO3. One engine, every platform, no rewrites.
The Compute Core: 21 Crates, One Engine
Mog's compute core is organized into 21 Rust crates, each responsible for a distinct domain: formula parsing, evaluation, dependency tracking, cell storage, XLSX serialization, and more. This modular structure lets us test and benchmark each subsystem independently while keeping compilation fast through incremental builds.
The engine implements 582 Excel-compatible functions, from basic arithmetic and string operations to statistical, financial, date/time, and lookup functions. Each function is registered in a dispatch table and shares a common evaluation context, so adding new functions follows a predictable pattern:
#[mog_function(category = "math")]fn ROUND(value: f64, digits: i32) -> f64 { let factor = 10f64.powi(digits); (value * factor).round() / factor}A custom bridge framework generates type-safe bindings for every target from annotated Rust source. This means the TypeScript SDK, Python package, and WASM module all share identical APIs without manual glue code.
Binary Wire Protocol: Zero-Alloc Rendering
The most unusual part of Mog's architecture is how the compute core communicates with the rendering layer. Instead of serializing cell data to JSON (which would mean thousands of allocations per frame), we designed a binary wire protocol optimized for viewport streaming.
Each message consists of a 36-byte header followed by N cell records of 32 bytes each:
Header (36B): [4B magic] [4B version] [4B viewport_x] [4B viewport_y] [4B viewport_w] [4B viewport_h] [4B cell_count] [4B flags] [4B checksum]
Cell Record (32B): [4B row] [4B col] [8B value] [4B format_id] [4B style_id] [4B flags] [4B reserved]The renderer reads this buffer directly into typed arrays with zero intermediate allocations. On a typical viewport of 500 visible cells, the entire payload is under 16KB — small enough to process in a single frame at 60fps.
CRDT Collaboration: Conflict-Free by Design
Real-time collaboration in Mog is built on Yrs, the Rust port of the Yjs CRDT framework. Every cell edit, structural change, and formatting operation is encoded as a CRDT operation that merges deterministically regardless of network ordering.
The key insight is our cell identity model. Every cell is keyed by a stable UUID rather than its row/column position. When two users concurrently insert rows, the CRDTs merge the structural changes and each cell retains its identity. Formulas referencing those cells resolve correctly after the merge because references point to UUIDs, not coordinates.
This design means offline editing works naturally. A user can disconnect, make extensive changes, and reconnect — all edits merge without conflicts or data loss.
OS-Style Architecture: Contracts, Kernel, Shell, Apps
At a higher level, Mog follows an OS-style layered architecture:
- Contracts — Shared TypeScript interfaces and types that define the API surface. Every layer depends on contracts, never on concrete implementations.
- Kernel — The Rust compute core plus the bridge layer. Handles formula evaluation, cell storage, file I/O, and collaboration transport.
- Shell — The rendering engine, viewport manager, and interaction layer. Draws pixels on canvas, handles input events, manages selection and editing state.
- Apps — View implementations (Grid, Kanban, Timeline, Calendar, Gallery, Form) built on top of the shell layer.
This separation means you can use the kernel without the shell (server-side XLSX processing), the shell without specific apps (custom view implementations), or the full stack for a complete spreadsheet experience.
Learn More
This post covers the highlights, but there is much more detail in our architecture documentation. For the full reference — including dependency graphs, data flow diagrams, and module inventories — see the Architecture Guide.