Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
96: BinaryHeap for Priority Queues
I've been thinking about how to handle a task scheduler for a simulation I'm working on. I have a bunch of events that need to happen, but they aren't all equal. Some are critical system interrupts that need to happen now, and others are just background cleanup tasks. My first instinct was to just throw them in a Vec and sort it, but that felt... clumsy. Let's see how that actually looks in code first.
The sorting bottleneck
struct Task {
priority: i32,
description: String,
}
fn main() {
let mut tasks = Vec::new();
tasks.push(Task { priority: 10, description: "Low priority cleanup".into() });
tasks.push(Task { priority: 100, description: "CRITICAL ERROR".into() });
tasks.push(Task { priority: 50, description: "User input".into() });
// To get the highest priority, I have to sort the whole thing
tasks.sort_by(|a, b| b.priority.cmp(&a.priority));
if let Some(top_task) = tasks.pop() {
println!("Executing: {}", top_task.description);
}
}
Wait, I just realized a huge problem here. If I'm adding tasks constantly while the program is running, I'd have to call sort_by every single time I push a new element if I want to maintain that order. That's an $O(n \log n)$ operation just to add one item. In a high-performance system, that's a non-starter. I need something that keeps the "most important" item at the top without me having to manually reorganize the whole list.
Switching to a BinaryHeap
This is exactly where std::collections::BinaryHeap comes in. It's essentially a priority queue. Instead of a linear list, it uses a tree structure (a max-heap) to ensure that the element with the greatest value is always at the front. Let's swap the Vec for a BinaryHeap and see what happens.
use std::collections::BinaryHeap;
#[derive(Eq, PartialEq)]
struct Task {
priority: i32,
description: String,
}
// We need to implement Ord and PartialOrd so the heap knows how to compare Tasks
impl Ord for Task {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.priority.cmp(&other.priority)
}
}
impl PartialOrd for Task {
fn partial_cmp(&self, other: &Self) -> Option {
Some(self.cmp(other))
}
}
fn main() {
let mut heap = BinaryHeap::new();
heap.push(Task { priority: 10, description: "Low priority cleanup".into() });
heap.push(Task { priority: 100, description: "CRITICAL ERROR".into() });
heap.push(Task { priority: 50, description: "User input".into() });
while let Some(task) = heap.pop() {
println!("Executing: {} (Priority: {})", task.description, task.priority);
}
}
That's much better. The push and pop operations are now $O(\log n)$. But notice something about the output: it gave me the "CRITICAL ERROR" (100) first. That's because BinaryHeap is a max-heap by default. It always pops the largest value.
The "Lowest Number is Highest Priority" Problem
In many real-world APIs, "Priority 1" is the most urgent, and "Priority 10" is the least. If I use the code above with that logic, my most urgent tasks will actually be the last ones to execute. I can't just change the Ord implementation to be reversed because that might confuse other parts of my code that expect standard integer comparison.
The idiomatic Rust way to handle this without rewriting my Ord logic is to use std::cmp::Reverse. It's a little wrapper that flips the result of any comparison.
use std::collections::BinaryHeap;
use std::cmp::Reverse;
fn main() {
// We store a tuple of (priority, description)
// We wrap the priority in Reverse() to turn the max-heap into a min-heap
let mut heap = BinaryHeap::new();
heap.push(Reverse((10, "Low priority cleanup")));
heap.push(Reverse((1, "URGENT: System Failure")));
heap.push(Reverse((5, "User input")));
while let Some(Reverse((priority, desc))) = heap.pop() {
println!("Executing: {} (Priority: {})", desc, priority);
}
}
Now, the task with priority 1 comes out first. By wrapping the value in Reverse, I've effectively told the BinaryHeap: "Whenever you compare these two items, act as if the smaller number is actually the larger one." It's a clean way to get a min-heap without fighting the standard library.
📋 Practical Task
Project: Emergency Room Triage System
Build a triage system for a hospital emergency room. You need to create a program that manages a queue of patients based on their severity level.
- Define a
Patientstruct with aname(String) and aseverity(integer, where 1 is the most critical/urgent and 5 is the least). - Implement the necessary traits (
Eq,PartialEq,Ord,PartialOrd) so that patients can be compared based on their severity. - Use a
BinaryHeapto store the patients. Ensure that when youpop()from the heap, the patient with the lowest severity number (the most urgent) is returned first. - Add at least five patients with varying severity levels and names to the heap in random order.
- Print the patients in the order they should be treated.
There are no comments for now.