我具有以下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
使用其他排序规则?
答案 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>
以外的多种指针类型,例如Task
或Rc<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
,因此请记住这一点。