13

我有一个新类型,我想实现Ord

use std::cmp::{Ord, Ordering};

struct MyType(isize);

impl Ord for MyType {
    fn cmp(&self, &other: Self) -> Ordering {
        let MyType(ref lhs) = *self;
        let MyType(ref rhs) = *other;
        lhs.cmp(rhs)
    }
}

当我尝试比较我的类型的两个变量时,出现错误:

error[E0277]: the trait bound `MyType: std::cmp::PartialOrd` is not satisfied
 --> src/main.rs:5:6
  |
5 | impl Ord for MyType {
  |      ^^^ can't compare `MyType` with `MyType`
  |
  = help: the trait `std::cmp::PartialOrd` is not implemented for `MyType`

error[E0277]: the trait bound `MyType: std::cmp::Eq` is not satisfied
 --> src/main.rs:5:6
  |
5 | impl Ord for MyType {
  |      ^^^ the trait `std::cmp::Eq` is not implemented for `MyType`

当我实现PartialEq,EqPartialOrd( gt(), lt(), eq(), ge(), le(), 等) 时,一切正常,但如果我提供了cmp,我们可以推断出lt()eq()!这是多余的!我不喜欢这个!

在查看文档时,我在以下定义中看到了这一点Ord

pub trait Ord: Eq + PartialOrd<Self> 

这看起来像 trait 继承自Eqand PartialOrd。为什么 trait 不能使用该函数从继承的 trait 中为所需方法提供默认实现cmp?我不知道特征的继承是如何工作的,搜索没有任何用处,但我认为这应该是可能的。

这在 Rust 中是如何完成的?我希望不是这样...

4

3 回答 3

14

对于您的具体情况,我会使用#[derive]

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct MyType(isize);

fn main() {
    let a = MyType(5);
    let b = MyType(6);

    println!("{:?}", a.cmp(&b))
}

如果您需要对您的Ord实现进行特殊处理,您仍然必须写出该代码,没有什么可以拯救我们!如果这样做有意义,您仍然可以派生其他特征的实现。

你问题的另一部分是模棱两可的,所以我会以两种方式回答:

为什么不能Ord由任何带有 的东西自动提供PartialOrd

让我们看一下 和 的PartialOrd文档Ord

PartialOrd说:“比较必须满足反对称传递性”,而Ord说:“形成总秩序的类型”。这些是数学术语,我不会像维基百科那样描述它们。

但是,我们可以使用浮点数作为示例。浮点数有一个称为 的特殊值NaN。与这个值进行比较是很棘手的。例如,所有的1.0 < NaN1.0 == NaN都是1.0 > NaN假的!这些不构成总顺序,但我们仍然可以将一个值与另一个值进行比较。这就是PartialOrd存在的原因——允许我们比较这样的类型。顺便说一句,PartialEq出于类似的原因而存在。

我们无法定义OrdPartialOrd因为我们对底层类型没有适当的保证。这是 Rust 的类型系统使我们免于犯错!

为什么不能PartialOrd由任何带有 的东西自动提供Ord

这里的问题是类型PartialOrd多于它们Ord。如果我们要求一切都是Ord,那么我们就不能进行任何浮点比较,因为它们没有全序,否则我们将不得不放弃全序并失去它提供的安全性.

但是,有一些想法可以自动为derive. 提议的RFC 2385将允许将上述代码简化为:

#[derive(Debug, Copy, Eq, Ord)]
struct MyType(isize);

当我实施PartialOrd( gt(), lt(), eq(), ge(), le()...)

请注意,PartialOrd 确实有默认实现,您只需要实现partial_cmp. 其他是出于易用性或可能的性能原因。

于 2015-02-07T22:23:19.670 回答
9

对于初学者,您只能实现PartialOrd::partial_cmp, as lt, le, gt, 并ge具有默认实现。此外,如果您实现Ord,您可以cmp像往常一样简单地实现并partial_cmp变成 just Some(self.cmp(other))

但是,如果您只想委托某个领域的平等和排序概念,那么推导会更好更容易:

#[derive(PartialOrd, Ord, PartialEq, Eq)]
struct MyType(isize);
于 2015-02-07T22:20:45.640 回答
4

请将此视为原始答案的附录,适合您的具体情况。该答案涉及您问题的第二部分。

考虑这个结构:

struct Person {
    id: u32,
    name: String,
    height: u32,
}

平等:PartialEqEq特征

PartialEq Trait,来自文档

Trait for equality comparisons which are partial equivalence 
relations. This trait allows for partial equality, for types that do not
have a full equivalence relation. For example, in floating point numbers
NaN != NaN, so floating point types implement PartialEq but not Eq.

Formally, the equality must be (for all a, b and c):

symmetric: a == b implies b == a; and
transitive: a == b and b == c implies a == c.

所以如果你想表达你的类型的值相等意味着什么,你必须实现这个PartialEq特征。实现它允许我们为我们的类型编写x == yx != y

impl PartialEq for Person {
    fn eq(&self, other: &Person) -> bool {
        self.height == other.height
    }
}

请注意,我们Person只是根据高度来决定 struct 的相等性。eq如果您想比较每个结构字段,您也可以实现此方法:

fn eq(&self, other: &Person) -> bool {
     self.id == other.id && self.name == other.name && self.height == other.height
}

但是,#[derive(PartialEq)]如果这是您想要的行为,那么简单地添加会更容易。

Eq Trait , 来自文档

Trait for equality comparisons which are equivalence relations.

This means, that in addition to a == b and a != b being strict inverses, 
the equality must be (for all a, b and c):

reflexive: a == a;
symmetric: a == b implies b == a; and
transitive: a == b and b == c implies a == c.

This property cannot be checked by the compiler, and therefore Eq implies
PartialEq, and has no extra methods.

Derivable
This trait can be used with #[derive]. When derived, because Eq has no extra methods, 
it is only informing the compiler that this is an equivalence relation rather than a 
partial equivalence relation. Note that the derive strategy requires all 
fields are Eq, which isn't always desired.

PartialEq 适用于不一定是自反的关系(即可以有这样的 x 使得 x != x)并且 Eq 是一个标记特征,它表示关系也是自反的(现在它是一个适当的等价关系)。

您还可以Eq使用空的 impl 块手动实现特征

impl Eq for Person {}

Eq但是,同样,添加到您的#[derive(Eq)]列表更容易。

排序:PartialOrdOrd特征

<使用运算符、<=和计算值>=的相对顺序>。要为您自己的类型实现这些,您必须实现PartialOrd特征。

在你可以实施之前PartialOrd,你必须实施PartialEq

impl PartialOrd for Person {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))    
    }
}

Ordering是具有这些值的枚举:

pub enum Ordering {
    Less,
    Equal,
    Greater,
}

partial_cmp返回 anOption而不是 an,Ordering因为有些类型的值不能始终进行比较,例如浮点数。NaNs 不是可表示的数字;诸如3.0 < NaN没有任何意义的表达。在这些情况下,partial_cmp返回None. 浮点值是标准库中唯一发生这种情况的情况。更多可以在这里找到。

partial_cmp返回 an的事实Option<Ordering>有一个结果:可能无法将两个值 x 和 y 置于明确的顺序中。实际上,这意味着实施PartialOrd不足以使您的值可排序。您还需要实现该Ord特征。

在你可以实施之前Ord,你必须先实施PartialOrdEq并且PartialEq

对于我们的Person结构,我们可以再次委托给我们的成员变量之一:

impl Ord for Person {
    fn cmp(&self, other: &Person) -> Ordering {
        self.height.cmp(&other.height)
    }
}
于 2020-05-27T13:04:14.563 回答