Collection Adapters and Specialized Types

Choosing Specialized Collections

When a workload moves beyond a plain growable array (Vec<T>) or key-value table (HashMap<K, V>), selecting the right specialized collection or adapter ensures optimal time complexity and cache behavior.

Quick Selection Guide for Specialized Collections

Workload / Access Pattern Best Specialized Choice Key Reason
FIFO queue, BFS traversal, or sliding window buffer VecDeque<T> Amortized $O(1)$ push_back and $O(1)$ pop_front in a contiguous ring buffer
In-memory byte pipe (Read + Write) VecDeque<u8> Implements std::io::Read (front) and std::io::Write (back) directly
Max-priority queue (highest priority first) BinaryHeap<T> $O(1)$ peek, $O(\log n)$ push/pop inside a single Vec<T> allocation
Min-priority queue or streaming Top-$K$ largest BinaryHeap<Reverse<T>> Zero-cost Reverse<T> wrapper inverts Ord without custom comparator boilerplate
Double-ended priority queue (pop both min and max) BTreeSet<T> (or BTreeMap<T, usize>) $O(\log n)$ pop_first and pop_last
Priority queue with $O(\log n)$ cancel / reprioritize by ID BTreeSet<(Prio, Id)> + HashMap<Id, Prio> Direct $O(\log n)$ removal of (old_prio, id)
$O(1)$ concatenation of two huge independent lists LinkedList<T> list_a.append(&mut list_b) relinks four pointers in $O(1)$ time

Combining Specialized Collections: Rate-Limited Priority Dispatcher

A practical event dispatcher often combines VecDeque (for tracking timestamps in a sliding rate-limit window) with BinaryHeap (for dispatching the highest-priority pending message whenever capacity is available in the window):

use std::collections::{BinaryHeap, VecDeque};

struct RateLimitedDispatcher {
    window_ms: u64,
    max_per_window: usize,
    sent_timestamps: VecDeque<u64>,
    pending_jobs: BinaryHeap<(u8, &'static str)>, // (priority, job_name)
}

impl RateLimitedDispatcher {
    fn new(window_ms: u64, max_per_window: usize) -> Self {
        Self {
            window_ms,
            max_per_window,
            sent_timestamps: VecDeque::with_capacity(max_per_window),
            pending_jobs: BinaryHeap::new(),
        }
    }

    fn enqueue(&mut self, priority: u8, job: &'static str) {
        self.pending_jobs.push((priority, job));
    }

    fn poll(&mut self, now_ms: u64) -> Option<(&'static str, u8)> {
        // 1. Evict expired timestamps from the front of the VecDeque in O(1) each:
        while let Some(&oldest) = self.sent_timestamps.front() {
            if now_ms.saturating_sub(oldest) >= self.window_ms {
                self.sent_timestamps.pop_front();
            } else {
                break;
            }
        }

        // 2. If under the rate limit, pop the highest-priority job from BinaryHeap:
        if self.sent_timestamps.len() < self.max_per_window {
            let (prio, job) = self.pending_jobs.pop()?;
            self.sent_timestamps.push_back(now_ms);
            Some((job, prio))
        } else {
            None
        }
    }
}

fn main() {
    // Allow at most 2 jobs per 1000ms window:
    let mut dispatcher = RateLimitedDispatcher::new(1_000, 2);
    dispatcher.enqueue(10, "low_prio_sync");
    dispatcher.enqueue(90, "critical_alert");
    dispatcher.enqueue(50, "user_webhook");

    println!("@100ms:  {:?}", dispatcher.poll(100));
    println!("@200ms:  {:?}", dispatcher.poll(200));
    println!("@300ms:  {:?} (rate limited!)", dispatcher.poll(300));
    println!("@1150ms: {:?} (100ms slot expired)", dispatcher.poll(1_150));
}
@100ms:  Some(("critical_alert", 90))
@200ms:  Some(("user_webhook", 50))
@300ms:  None (rate limited!)
@1150ms: Some(("low_prio_sync", 10)) (100ms slot expired)