是否可以通过委托给结构成员来创建实现 Ord 的宏?
Posted
技术标签:
【中文标题】是否可以通过委托给结构成员来创建实现 Ord 的宏?【英文标题】:Is it possible to create a macro that implements Ord by delegating to a struct member? 【发布时间】:2018-01-21 16:40:07 【问题描述】:我有一个结构:
struct Student
first_name: String,
last_name: String,
我想创建一个可以按last_name
排序的Vec<Student>
。我需要实现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
如果您有很多带有明显字段要排序的结构,这可能会非常单调和重复。是否可以创建一个宏来自动实现这一点?
类似:
impl_ord!(Student, Student.last_name)
我找到了Automatically implement traits of enclosed type for Rust newtypes (tuple structs with one field),但这不是我想要的。
【问题讨论】:
鉴于Student
作为一种暗示特定排序顺序的数据类型没有任何意义,我会认为 sort_by
似乎是要走的路。
【参考方案1】:
我创建了一个宏,它允许通过定义用于比较元素的表达式来实现Ord
:ord_by_key::ord_eq_by_key_selector,类似于您所要求的。
use ord_by_key::ord_eq_by_key_selector;
#[ord_eq_by_key_selector(|s| &s.last_name)]
struct Student
first_name: String,
last_name: String,
如果您必须在不同情况下按照不同的标准进行排序,您可以为您的结构引入一个容器,该容器将实现不同的排序策略:
use ord_by_key::ord_eq_by_key_selector;
struct Student
first_name: String,
last_name: String,
#[ord_eq_by_key_selector(|(s)| &s.first_name)]
struct StudentByFirstName(Student);
#[ord_eq_by_key_selector(|(s)| &s.last_name, &s.first_name)]
struct StudentByLastNameAndFirstName(Student);
【讨论】:
【参考方案2】:是的,你可以,但首先:请阅读为什么你不应该!
为什么不呢?
当一个类型实现Ord
或PartialOrd
时,这意味着该类型具有自然排序,这反过来意味着实现的排序是唯一的逻辑排序。取整数:3 自然小于 4。当然,还有其他有用的排序。您可以使用倒序对整数进行降序排序,但只有 一个 自然的。
现在你有了一个由两个字符串组成的类型。有自然排序吗?我声称:不!有很多有用的排序,但是按姓氏排序比按名字排序更自然吗?我不这么认为。
那该怎么做呢?
还有另外两种排序方法:
sort_by()
,和
sort_by_key()
.
两者都允许您修改排序算法比较值的方式。按姓氏排序可以这样(full code):
students.sort_by(|a, b| a.last_name.cmp(&b.last_name));
这样,您可以指定如何对每个方法调用进行排序。有时您可能希望按姓氏排序,而有时您希望按名字排序。由于没有明显且自然的排序方式,因此您不应将任何特定的排序方式“附加”到类型本身。
但是说真的,我想要一个宏...
当然,在 Rust 中可以编写这样的宏。一旦你了解了宏观系统,这实际上很容易。但是,我们不要为您的Student
示例这样做,因为——我希望你现在已经理解——这是一个坏主意。
什么时候是个好主意?当只有一个字段语义上是类型的一部分时。取这个数据结构:
struct Foo
actual_data: String,
_internal_cache: String,
这里,_internal_cache
在语义上不属于您的类型。这只是一个实现细节,因此对于Eq
和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
为什么我把你问的这么一大段代码称为简单?好吧,这段代码的大部分内容正是您已经编写的:impls
。我执行了两个简单的步骤:
-
在您的代码周围添加宏定义并考虑我们需要哪些参数(
type_name
和 field
)
将所有提到的Student
替换为$type_name
,并将所有提到的last_name
替换为$field
这就是为什么它被称为“示例宏”的原因:您基本上只是将普通代码编写为示例,但可以根据参数使其部分可变。
你可以测试整个东西here。
【讨论】:
啊,我明白了。谢谢,这很棒。你看,我尝试使用sort_by_key(|a| a.last_name)
并收到错误“无法移出借来的内容”。我不知道为什么我会这样做(但是,嘿,我正在学习)。但这让我想到了这个:试着弄清楚宏是如何工作的。所以伤害了,吸取了教训 :) 再次感谢您的清晰而彻底的回应!
不客气!您的sort_by_key()
调用有一个不同的问题,可以这样解决:sort_by_key(|a| a.last_name.clone())
但它非常慢!更好的解决方案是sort_by_key(|a| &a.last_name)
,但这不起作用(我们正在讨论这个问题in the chat)。哦,顺便说一句,使用 StackOverfow 的提示:如果您喜欢并接受答案,通常应该支持它。否则,回答者经常会陷入绝望,想知道为什么他/她不值得投票^_^
别担心,我把你的教训牢记在心。但我确实想指出一个可能的用例。你看,我有另一个结构Grade
与这个结构相关联:struct Grade grade: u8, student_roster: Vec<Student>,
。在我看来,这个在Vec<Grade>
中有一个自然排序。一年级Ord 的代码可能有点乏味且不干燥。但是,嘿,我是一个菜鸟,所以也许我以后会有不同的意见。
我试过了!我得到一个错误,我没有足够的重复:P 我需要一些像 15 (我在这里很新,呵呵)。如果可以的话,兄弟。
@Haffix 绝对有很多用例!但是许多来自其他语言的人倾向于实现Ord
,以便他们可以在特定情况下使用sort
。我的回答只是想让读者意识到他们考虑“自然排序”的结果的可能性。最后,程序员必须决定;-)以上是关于是否可以通过委托给结构成员来创建实现 Ord 的宏?的主要内容,如果未能解决你的问题,请参考以下文章