๐Ÿฆ€ Functional Rust

969: Circular Buffer

Difficulty: Intermediate Category: Data Structures Concept: Fixed-capacity ring buffer that overwrites the oldest element when full Key Insight: Both languages maintain `head`, `tail`, and `count` indices with `% capacity` modular arithmetic โ€” OCaml uses mutable record fields, Rust uses a struct with `let mut` fields; the modular wrap-around logic is character-for-character identical
// 969: Circular / Ring Buffer
// Fixed-capacity FIFO: push overwrites oldest when full
// OCaml: array + head/tail/count refs; Rust: Vec + indices

pub struct RingBuffer<T> {
    data: Vec<T>,
    capacity: usize,
    head: usize, // next read index
    tail: usize, // next write index
    count: usize,
}

impl<T: Default + Clone> RingBuffer<T> {
    pub fn new(capacity: usize) -> Self {
        assert!(capacity > 0, "capacity must be > 0");
        RingBuffer {
            data: vec![T::default(); capacity],
            capacity,
            head: 0,
            tail: 0,
            count: 0,
        }
    }
}

impl<T: Clone> RingBuffer<T> {
    pub fn is_full(&self) -> bool { self.count == self.capacity }
    pub fn is_empty(&self) -> bool { self.count == 0 }
    pub fn size(&self) -> usize { self.count }

    /// Push: if full, overwrites oldest element
    pub fn push(&mut self, x: T) {
        self.data[self.tail] = x;
        self.tail = (self.tail + 1) % self.capacity;
        if self.is_full() {
            // Overwrite: advance head (drop oldest)
            self.head = (self.head + 1) % self.capacity;
        } else {
            self.count += 1;
        }
    }

    /// Pop: removes and returns oldest element
    pub fn pop(&mut self) -> Option<T> {
        if self.is_empty() {
            None
        } else {
            let x = self.data[self.head].clone();
            self.head = (self.head + 1) % self.capacity;
            self.count -= 1;
            Some(x)
        }
    }

    /// Peek: view oldest without removing
    pub fn peek(&self) -> Option<&T> {
        if self.is_empty() {
            None
        } else {
            Some(&self.data[self.head])
        }
    }

    /// Collect all elements in order (oldest first)
    pub fn to_vec(&self) -> Vec<T> {
        (0..self.count)
            .map(|i| self.data[(self.head + i) % self.capacity].clone())
            .collect()
    }
}

fn main() {
    let mut r: RingBuffer<i32> = RingBuffer::new(4);
    r.push(1);
    r.push(2);
    r.push(3);
    r.push(4);

    println!("full: {}, size: {}", r.is_full(), r.size());
    println!("peek: {:?}", r.peek());

    r.push(5); // overwrites 1
    println!("after push(5), peek: {:?}", r.peek());
    println!("contents: {:?}", r.to_vec());

    println!("pop: {:?}", r.pop());
    println!("pop: {:?}", r.pop());
    println!("remaining: {:?}", r.to_vec());
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_basic_push_pop() {
        let mut r: RingBuffer<i32> = RingBuffer::new(4);
        assert!(r.is_empty());
        r.push(1);
        r.push(2);
        r.push(3);
        r.push(4);
        assert!(r.is_full());
        assert_eq!(r.size(), 4);
        assert_eq!(r.peek(), Some(&1));
    }

    #[test]
    fn test_overwrite_on_full() {
        let mut r: RingBuffer<i32> = RingBuffer::new(4);
        r.push(1);
        r.push(2);
        r.push(3);
        r.push(4);
        r.push(5); // overwrites 1
        assert_eq!(r.size(), 4);
        assert_eq!(r.peek(), Some(&2));
        assert_eq!(r.to_vec(), vec![2, 3, 4, 5]);
    }

    #[test]
    fn test_pop_order() {
        let mut r: RingBuffer<i32> = RingBuffer::new(4);
        for i in 1..=4 { r.push(i); }
        assert_eq!(r.pop(), Some(1));
        assert_eq!(r.pop(), Some(2));
        assert_eq!(r.size(), 2);
    }

    #[test]
    fn test_wrap_around() {
        let mut r: RingBuffer<i32> = RingBuffer::new(4);
        for i in 1..=4 { r.push(i); }
        r.pop();
        r.pop();
        r.push(5);
        r.push(6);
        r.push(7); // overwrite
        assert_eq!(r.to_vec(), vec![4, 5, 6, 7]);
    }

    #[test]
    fn test_empty_pop() {
        let mut r: RingBuffer<i32> = RingBuffer::new(2);
        assert_eq!(r.pop(), None);
        assert_eq!(r.peek(), None);
    }
}
(* 969: Circular/Ring Buffer *)
(* Fixed-capacity FIFO. Write overwrites oldest when full. *)

type 'a ring = {
  data: 'a array;
  capacity: int;
  mutable head: int;   (* index to read next *)
  mutable tail: int;   (* index to write next *)
  mutable count: int;
}

let create capacity default =
  { data = Array.make capacity default;
    capacity;
    head = 0;
    tail = 0;
    count = 0 }

let is_full r = r.count = r.capacity
let is_empty r = r.count = 0
let size r = r.count

(* Push: if full, overwrite oldest (advance head) *)
let push r x =
  if is_full r then begin
    r.data.(r.tail) <- x;
    r.tail <- (r.tail + 1) mod r.capacity;
    r.head <- (r.head + 1) mod r.capacity  (* overwrite oldest *)
  end else begin
    r.data.(r.tail) <- x;
    r.tail <- (r.tail + 1) mod r.capacity;
    r.count <- r.count + 1
  end

(* Pop: returns oldest element *)
let pop r =
  if is_empty r then None
  else begin
    let x = r.data.(r.head) in
    r.head <- (r.head + 1) mod r.capacity;
    r.count <- r.count - 1;
    Some x
  end

let peek r =
  if is_empty r then None
  else Some r.data.(r.head)

let () =
  let r = create 4 0 in
  assert (is_empty r);

  push r 1;
  push r 2;
  push r 3;
  push r 4;
  assert (is_full r);
  assert (size r = 4);
  assert (peek r = Some 1);

  (* Overwrite: push 5 overwrites 1 *)
  push r 5;
  assert (size r = 4);
  assert (peek r = Some 2);

  assert (pop r = Some 2);
  assert (pop r = Some 3);
  assert (size r = 2);

  push r 6;
  push r 7;
  push r 8;
  assert (is_full r);

  assert (pop r = Some 4);
  assert (pop r = Some 5);
  assert (pop r = Some 6);
  assert (pop r = Some 7);
  assert (pop r = Some 8);
  assert (is_empty r);
  assert (pop r = None);

  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Circular Buffer โ€” Comparison

Core Insight

A ring buffer uses modular arithmetic (`i = (i + 1) % capacity`) to wrap around array indices, creating the illusion of a circular structure from a linear array. Three indices (`head`, `tail`, `count`) fully describe state. Both OCaml and Rust implement this identically; the main difference is OCaml uses `mutable` record fields vs Rust's `let mut` struct fields.

OCaml Approach

  • `mutable head/tail/count` in a record
  • `Array.make capacity default` โ€” requires a default value
  • `r.head <- (r.head + 1) mod r.capacity` โ€” modular advance
  • Handles full/empty via `count` field
  • Overwrite policy: advance both tail and head when full

Rust Approach

  • Struct with `head: usize`, `tail: usize`, `count: usize`
  • `vec![T::default(); capacity]` โ€” requires `T: Default + Clone`
  • `self.tail = (self.tail + 1) % self.capacity` โ€” same modular advance
  • Check `is_full()` before incrementing count (slightly different branch order)
  • `to_vec()` for snapshot: `(0..count).map(|i| data[(head+i)%cap])`

Comparison Table

AspectOCamlRust
StateMutable record fields`&mut self` struct
Array init`Array.make cap default``vec![T::default(); cap]`
Modular advance`(i + 1) mod capacity``(i + 1) % capacity`
Full check`count = capacity``count == capacity`
OverwriteAdvance both head and tailAdvance head after tail write
SnapshotManual fold`.map(idata[(head+i)%cap])`
Generic`'a ring``RingBuffer<T>` with `T: Clone`