In this lesson, you will learn how to maintain a continuous, fixed-size window over an incoming stream of data without reallocating memory by using a cyclic buffer.
The Problem with Continuous Streams
When processing a high-frequency stream of data (like sensor readings or network packets), you often need to perform calculations on the "last N items." If you create a new Vec or allocate a new buffer for every new batch of data, your program will spend most of its time interacting with the allocator, leading to memory fragmentation and poor performance.
The Cyclic Buffer Strategy
A cyclic (or circular) buffer is a fixed-size array where the "start" and "end" indices wrap around to the beginning when they hit the boundary. Because we are managing the buffer's lifetime explicitly, we can reuse the same memory block indefinitely.
In Rust, this requires careful pointer arithmetic or index management. Since the buffer has a static or long-lived scope, we can hold a reference to it while sliding our window forward.
Implementation Example
Consider a window of 3 elements:
struct Window<'a> {
data: &'a mut [f64],
cursor: usize,
}
impl<'a> Window<'a> {
fn push(&mut self, value: f64) {
// Overwrite the oldest element at the cursor
self.data[self.cursor] = value;
// Move cursor, wrapping around to 0 if we exceed length
self.cursor = (self.cursor + 1) % self.data.len();
}
}
Why Lifetimes Matter
By keeping the buffer's lifetime bound to the stream processor, we ensure that the data remains valid throughout the object's existence. Because we use a mutable slice (&'a mut [f64]), the compiler enforces that only one part of our program is modifying the window at a time. This pattern is essentially a "sliding view" over a static piece of memory. When the cursor wraps around, we aren't changing the memory layout; we are simply re-mapping our logical window to physical memory that already exists.
This approach eliminates the need for expensive heap operations, allowing your stream processing logic to remain deterministic and high-performance.
Key Takeaway: Use cyclic buffers and mutable slices to implement fixed-window stream processing, ensuring zero-allocation updates by wrapping indices instead of reallocating memory.