If you want to have multiple types of Task
s in the same BinaryHeap
(at the same time), you're going to need to use a boxed trait object, or some other kind of (smart) pointer. So you should implement Ord
on the type Box<dyn Task>
. See below if you really are just using one type of task at a time.
use std::cmp::Ordering;
use std::collections::BinaryHeap;
trait Task {
fn time(&self) -> std::time::Duration;
fn run(self);
}
impl PartialEq for Box<dyn Task> {
fn eq(&self, other: &Self) -> bool {
self.time() == other.time()
}
}
impl Eq for Box<dyn Task> {}
impl PartialOrd for Box<dyn Task> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(&other)) // Delegate to the implementation in `Ord`.
}
}
impl Ord for Box<dyn Task> {
fn cmp(&self, other: &Self) -> Ordering {
self.time().cmp(&other.time())
}
}
Then when you insert a Task
into the BinaryHeap
, you'll need to box it with Box::new
or something equivalent. Since this Box<dyn Task>
may outlive any references in T
, you'll have to ensure that T
contains at most 'static
references by adding 'static
to the bounds on T
.
fn foo<T: Task + 'static>(task: T) {
let mut heap = BinaryHeap::new();
let boxed_task: Box<dyn Task> = Box::new(task);
heap.push(boxed_task);
}
Just a side note: you could also implement Ord
(and the rest) on dyn Task
. Then you would get an implementation of Ord
for Box<dyn Task>
for free since Box<T>
implements Ord
if T
does. This could be useful if you're using multiple pointer types other than Box<T>
with Task
s, like Rc<dyn Task>
or &dyn Task
.
If you really are just using one type at a time, then you could use a wrapper type that implements Ord
. This looks like what you're doing right now since you only create the BinaryHeap
once you have a specific task in hand. I don't think this is what you want, but here it is anyway.
use std::cmp::Ordering;
use std::collections::BinaryHeap;
trait Task {
fn time(&self) -> std::time::Duration;
fn run(self);
}
struct TaskWrapper<T: Task>(T);
impl<T: Task> PartialEq for TaskWrapper<T> {
fn eq(&self, other: &Self) -> bool {
self.0.time() == other.0.time()
}
}
impl<T: Task> Eq for TaskWrapper<T> {}
impl<T: Task> Ord for TaskWrapper<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.0.time().cmp(&other.0.time())
}
}
impl<T: Task> PartialOrd for TaskWrapper<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(&other))
}
}
With this approach, if task: T
and T
implements Task
, you can insert task
into the BinaryHeap
by wrapping it like TaskWrapper(task)
.
fn foo<T: Task>(task: T) {
let mut heap = BinaryHeap::new();
heap.push(TaskWrapper(task));
}
Just one more thing. It's a logic error for the relative ordering of members of a BinaryHeap
to change. Since our ordering here is based entirely off of the method time
, the ordering might change every time time
is called. So even in this situation, we have to depend on the correctness external implementation of time
. You wanted to avoid using the trait bound T: Ord
for exactly this reason, so bear this in mind.