use std::cmp::{Ordering, Reverse};
#[derive(Debug, Clone)]
pub struct ScheduledTask<T> {
pub priority: u8,
pub seq: u64,
pub payload: T,
}
impl<T> PartialEq for ScheduledTask<T> {
fn eq(&self, other: &Self) -> bool {
self.priority == other.priority && self.seq == other.seq
}
}
impl<T> Eq for ScheduledTask<T> {}
impl<T> Ord for ScheduledTask<T> {
fn cmp(&self, other: &Self) -> Ordering {
// Higher priority first; on ties, earlier seq wins via Reverse.
self.priority
.cmp(&other.priority)
.then_with(|| Reverse(self.seq).cmp(&Reverse(other.seq)))
}
}
impl<T> PartialOrd for ScheduledTask<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
use std::collections::BinaryHeap;
use crate::task::ScheduledTask;
pub struct Scheduler<T> {
heap: BinaryHeap<ScheduledTask<T>>,
next_seq: u64,
}
impl<T> Scheduler<T> {
pub fn new() -> Self {
Scheduler {
heap: BinaryHeap::new(),
next_seq: 0,
}
}
pub fn push(&mut self, priority: u8, payload: T) {
let seq = self.next_seq;
self.next_seq += 1;
self.heap.push(ScheduledTask { priority, seq, payload });
}
pub fn pop(&mut self) -> Option<ScheduledTask<T>> {
self.heap.pop()
}
pub fn peek(&self) -> Option<&ScheduledTask<T>> {
self.heap.peek()
}
pub fn len(&self) -> usize {
self.heap.len()
}
pub fn is_empty(&self) -> bool {
self.heap.is_empty()
}
pub fn drain_ready<F>(&mut self, mut ready: F) -> Vec<ScheduledTask<T>>
where
F: FnMut(&ScheduledTask<T>) -> bool,
{
let mut out = Vec::new();
while let Some(top) = self.heap.peek() {
if !ready(top) {
break;
}
out.push(self.heap.pop().unwrap());
}
out
}
}
mod scheduler;
mod task;
use scheduler::Scheduler;
fn main() {
let mut sched: Scheduler<String> = Scheduler::new();
sched.push(1, "send digest email".into());
sched.push(9, "charge failed payment".into());
sched.push(5, "resize avatar".into());
sched.push(9, "revoke leaked token".into());
sched.push(5, "generate thumbnail".into());
println!("next up: {:?}", sched.peek().map(|t| &t.payload));
// Drain everything at priority 9 or higher first.
let urgent = sched.drain_ready(|t| t.priority >= 9);
for t in &urgent {
println!("[urgent p{}] {}", t.priority, t.payload);
}
while let Some(t) = sched.pop() {
println!("[p{} seq{}] {}", t.priority, t.seq, t.payload);
}
}
This snippet builds a small priority-based task scheduler on top of Rust's std::collections::BinaryHeap, which is a max-heap by default. The core challenge with a BinaryHeap scheduler is controlling exactly what "highest priority" means, and that control comes entirely from the Ord implementation on the item stored in the heap.
In ScheduledTask, a task carries a numeric priority, a monotonically increasing seq sequence number, and its payload. The manual Ord and PartialOrd implementations are the heart of the design: tasks compare primarily by priority (higher priority pops first, which the default max-heap gives for free), but ties break by seq wrapped in Reverse. Because Reverse inverts the comparison, a smaller sequence number is treated as greater, so the task enqueued earlier wins the tie. This is how a plain BinaryHeap is coerced into a stable, FIFO-within-priority ordering — something the heap does not provide on its own, since heaps are not stable.
The seq counter also solves a subtle correctness issue: without a tiebreaker, two tasks with equal priority would compare Equal, and the heap's internal order would be arbitrary and could even starve tasks. Threading a unique, ever-increasing counter through every push guarantees a total order.
In Scheduler, push assigns the next seq from an internal counter and wraps the task before inserting it, so callers never manage sequencing themselves. pop simply delegates to BinaryHeap::pop, which is an O(log n) operation that returns the maximum according to Ord. The peek method exposes the next task without removing it, and drain_ready demonstrates a common scheduler pattern: repeatedly popping while a predicate holds, useful for draining all tasks above a threshold or all tasks due before a deadline.
The main.rs tab exercises the scheduler, pushing tasks out of priority order and showing that they emerge highest-priority-first, with insertion order preserved among equal priorities. The trade-off of this approach is that BinaryHeap offers no efficient removal or reprioritization of an arbitrary task; if those operations were needed, an indexed heap or a BTreeMap-keyed structure would be a better fit. For a fire-and-forget priority scheduler, though, BinaryHeap is the idiomatic, allocation-friendly choice.
Related snips
struct Config<'a> {
name: &'a str,
value: &'a str,
}
fn parse_config(line: &str) -> Config {
Lifetime annotations for flexible borrowing in structs
use crossbeam::channel::unbounded;
use std::thread;
fn main() {
let (tx, rx) = unbounded();
Crossbeam for advanced concurrent data structures
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
use tracing::{info, instrument};
#[instrument]
fn process_request(user_id: u64) {
info!(user_id, "Processing request");
// Work happens here
tracing for structured logging and distributed tracing
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
Channels (mpsc) for message passing between threads
use clap::Parser;
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Args {
#[arg(short, long)]
clap for CLI argument parsing with derive macros
Share this code
Here's the card — post it anywhere.