rust 110 lines · 3 tabs

Priority Task Scheduler in Rust with BinaryHeap and Reverse Ordering

Shared by codesnips Aug 2026
3 tabs
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))
    }
}
3 files · rust Explain with highlit

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

Share this code

Here's the card — post it anywhere.

Priority Task Scheduler in Rust with BinaryHeap and Reverse Ordering — share card
Link copied