无法实现Ord时如何使用BinaryHeap?

时间:2019-07-17 17:20:46

标签: rust traits sortedlist binary-heap

我具有以下Rust特征:

trait Task {
    fn time(&self) -> std::time::Duration;
    fn run(self);
}

我需要将T: Task的实例保留在某种排序的列表中,在其中可以弹出time()的最小值的实例。我的计划是使用BinaryHeap

fn foo<T: Task>(task: T) {
    let heap = std::collections::BinaryHeap::new();
    heap.push(task);
}

但是,我在这里遇到麻烦。要将某物放入BinaryHeap中,它必须实现Ord

error[E0277]: the trait bound `T: std::cmp::Ord` is not satisfied
 --> src/lib.rs:7:16
  |
7 |     let heap = std::collections::BinaryHeap::new();
  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::cmp::Ord` is not implemented for `T`
  |
  = help: consider adding a `where T: std::cmp::Ord` bound
  = note: required by `std::collections::BinaryHeap::<T>::new`

我无法为Ord实现Task,因为Ord不是来自我的箱子。而且我也不想这样做,因为Task的实现者可能希望为Ord拥有其他实现。

那我该怎么办?我还可以使用其他排序列表吗?还是可以欺骗BinaryHeap使用其他排序规则?

1 个答案:

答案 0 :(得分:3)

如果您想在同一Task中同时使用多种类型的BinaryHeap,则需要使用一个装箱的特征对象或其他类型的特征对象(智能)指针。因此,您应该在类型Ord上实现Box<dyn Task>。如果您真的一次仅使用一种类型的任务,请参见下文。

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())
    }
}

然后,当您将Task插入BinaryHeap时,需要用Box::new或类似的东西将其装箱。由于此Box<dyn Task>可能比T中的任何引用都有效,因此您必须通过将T添加到边界上来确保'static最多包含'static个引用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);
}

请注意:您还可以在Ord上实现dyn Task(及其余)。然后,您将免费获得Ord的{​​{1}}的实现,因为如果Box<dyn Task>的话,Box<T>实现了Ord。如果您使用T以外的Box<T>以外的多种指针类型,例如TaskRc<dyn Task>,这可能很有用。


如果您确实一次只使用一种类型,则可以使用实现&dyn Task的包装器类型。这看起来像您现在正在做的事情,因为只有在完成特定任务后才创建Ord。我不认为这不是您想要的,但是无论如何,这还是。

BinaryHeap

使用这种方法,如果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)) } } task: T实现了T,则可以像Task一样将task插入BinaryHeap

TaskWrapper(task)

再说一件事。 fn foo<T: Task>(task: T) { let mut heap = BinaryHeap::new(); heap.push(TaskWrapper(task)); } 的成员的相对顺序更改是逻辑错误。由于我们的排序完全基于方法BinaryHeap,因此每次调用time时,排序可能都会改变。因此,即使在这种情况下,我们也必须依靠time的外部实现的正确性。正是出于这个原因,您想要避免使用特征绑定time,因此请记住这一点。