我有一个结构:
struct Student {
first_name: String,
last_name: String,
}
Run Code Online (Sandbox Code Playgroud)
我想创建一个Vec<Student>可以按last_name. 我需要实现Ord,PartialOrd并且PartialEq:
use std::cmp::Ordering;
impl Ord for Student {
fn cmp(&self, other: &Student) -> Ordering {
self.last_name.cmp(&other.last_name)
}
}
impl PartialOrd for Student {
fn partial_cmp(&self, other: &Student) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Student {
fn eq(&self, other: &Student) -> bool {
self.last_name == other.last_name
}
}
Run Code Online (Sandbox Code Playgroud)
如果您有很多结构体都有明显的字段可供排序,这可能会非常单调和重复。是否可以创建一个宏来自动实现这一点?
就像是:
impl_ord!(Student, Student.last_name)
Run Code Online (Sandbox Code Playgroud)
我发现自动实现 Rust newtypes (tuple structs with one field) 的封闭类型特征,但这并不是我想要的。
是的,你可以,但首先:请阅读为什么你不应该!
当一个类型实现Ord或PartialOrd意味着该类型具有自然排序时,这反过来意味着实现的排序是唯一的逻辑顺序。取整数:3 自然小于 4。当然还有其他有用的排序。您可以使用倒序对整数进行降序排序,但只有一个自然数。
现在你有一个由两个字符串组成的类型。有自然排序吗?我声明:不!有很多有用的排序方式,但是按姓氏排序是否比按名字排序更自然?我不这么认为。
还有另外两种排序方法:
两者都可以让您修改排序算法比较值的方式。按姓氏排序可以这样完成(完整代码):
students.sort_by(|a, b| a.last_name.cmp(&b.last_name));
Run Code Online (Sandbox Code Playgroud)
这样,您可以指定如何对每个方法调用进行排序。有时您可能想按姓氏排序,有时您想按名字排序。由于没有明显且自然的排序方式,因此您不应将任何特定的排序方式“附加”到类型本身。
当然,在 Rust 中可以编写这样的宏。一旦你理解了宏系统,这实际上很容易。但是让我们不要以您的Student示例为例,因为 - 正如我希望您现在理解的那样 - 这是一个坏主意。
什么时候是个好主意?当语义上只有一个字段是类型的一部分时。以这个数据结构为例:
struct Foo {
actual_data: String,
_internal_cache: String,
}
Run Code Online (Sandbox Code Playgroud)
在这里,在_internal_cache语义上不属于您的类型。这只是一个实现细节,因此应该忽略Eqand Ord。简单的宏是:
macro_rules! impl_ord {
($type_name:ident, $field:ident) => {
impl Ord for $type_name {
fn cmp(&self, other: &$type_name) -> Ordering {
self.$field.cmp(&other.$field)
}
}
impl PartialOrd for $type_name {
fn partial_cmp(&self, other: &$type_name) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for $type_name {
fn eq(&self, other: &$type_name) -> bool {
self.$field == other.$field
}
}
impl Eq for $type_name {}
}
}
Run Code Online (Sandbox Code Playgroud)
你问我为什么把这么大的代码称为简单?好吧,这些代码的绝大部分正是您已经编写的内容:impls. 我执行了两个简单的步骤:
type_name和field)Student与$type_name和所有提及的last_name与$field这就是为什么它被称为“示例宏”的原因:您基本上只是编写普通代码作为示例,但可以使每个参数的一部分可变。
您可以在这里测试整个过程。