RustLandia - Matrix Rain
April 18, 2026•561 words
This is a dissection of a simple Matrix Rain implementation i write in my Rust exploring journey. All the code is base on a simple data structure like this:
const GRID_ROWS: usize = 40;
const GRID_COLS: usize = 140;
// in main()
let mut grid: [[char; GRID_COLS]; GRID_ROWS] = [[' '; GRID_COLS]; GRID_ROWS];
let mut active_cols: [bool; GRID_COLS] = [false; GRID_COLS];
The grid and active_cols are fixed-size arrays, not vectors (Vec). This is a deliberate architectural choice. Because their size is known at compile time (thanks to const), Rust allocates them directly on the stack. There is no overhead from interacting with the system's memory allocator, which is what happens when you use heap-based structures like Vec. In a tight loop that runs continuously, avoiding heap allocations is a significant performance improvement.
Rust's ownership and borrowing system is on full display here. Notice the function signatures:
fn print_grid(g: &[[char; GRID_COLS]; GRID_ROWS]) { /* ... */ }
fn update_grid(g: &mut [[char; GRID_COLS]; GRID_ROWS], r: &[char; GRID_COLS]) { /* ... */ }
fn generate_line(ac: &mut [bool; GRID_COLS]) -> [char; GRID_COLS] { /* ... */ }
- print_grid takes an immutable reference (&). It promises the compiler it will only read from the grid. It cannot, under any circumstances, modify the data.
- update_grid and generate_line take mutable references (&mut). This grants them exclusive, temporary write access. While update_grid holds a mutable reference to grid, no other part of the program can read from or write to it.
An interesting choice is in update_grid function:
fn update_grid(g: &mut [[char; GRID_COLS]; GRID_ROWS], r: &[char; GRID_COLS]){
g.rotate_right(1);
g[0] = *r;
}
The line g[0] = *r; works because fixed-size arrays of Copy types (like char) are themselves Copy. When we dereference r (which is a reference &), we are not moving ownership; we are performing a cheap, bit-for-bit copy of the new line's data from the reference location into the first row of our grid slice. This is an explicit and efficient operation, again avoiding heap-related overhead.
Terminal rendering can be a bottleneck . Printing character by character or even line by line involves many system calls, which are expensive. The print_grid function demonstrates a classic Rust idiom for high-performance I/O.
fn print_grid(g: &[[char; GRID_COLS]; GRID_ROWS]){
let mut buf = String::with_capacity(GRID_ROWS * ( GRID_COLS + 1 ) + 8);
// ... build the entire screen frame in the buffer ...
let mut out = io::stdout().lock();
let _ = out.write_all(buf.as_bytes());
let _ = out.flush();
}
Pre-allocation with String::with_capacity: We calculate the exact size of the final string (all rows, all columns, plus newlines) and pre-allocate a buffer of that size. This ensures the String object performs exactly one heap allocation. As we push characters and strings into it, the buffer never needs to resize, which would involve costly reallocations and memory copies.
Locking stdout: io::stdout().lock() acquires an exclusive lock on the standard output handle. This allows for buffered writes. Instead of sending tiny chunks of data to the operating system, we write the entire buffer (buf.as_bytes()) in a single write_all call. This dramatically reduces the number of system calls, resulting in a much smoother, flicker-free animation.
You can find this implementation on GitHub: