是否可以通过委托给结构成员来创建实现 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>。我需要实现OrdPartialOrdPartialEq

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】:

是的,你可以,但首先:请阅读为什么你不应该


为什么不呢?

当一个类型实现OrdPartialOrd 时,这意味着该类型具有自然排序,这反过来意味着实现的排序是唯一的逻辑排序。取整数: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 在语义上不属于您的类型。这只是一个实现细节,因此对于EqOrd 应该被忽略。简单的宏是:

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_namefield) 将所有提到的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| &amp;a.last_name),但这不起作用(我们正在讨论这个问题in the chat)。哦,顺便说一句,使用 StackOverfow 的提示:如果您喜欢并接受答案,通常应该支持它。否则,回答者经常会陷入绝望,想知道为什么他/她不值得投票^_^ 别担心,我把你的教训牢记在心。但我确实想指出一个可能的用例。你看,我有另一个结构Grade 与这个结构相关联:struct Grade grade: u8, student_roster: Vec&lt;Student&gt;, 。在我看来,这个在Vec&lt;Grade&gt; 中有一个自然排序。一年级Ord 的代码可能有点乏味且不干燥。但是,嘿,我是一个菜鸟,所以也许我以后会有不同的意见。 我试过了!我得到一个错误,我没有足够的重复:P 我需要一些像 15 (我在这里很新,呵呵)。如果可以的话,兄弟。 @Haffix 绝对有很多用例!但是许多来自其他语言的人倾向于实现Ord,以便他们可以在特定情况下使用sort。我的回答只是想让读者意识到他们考虑“自然排序”的结果的可能性。最后,程序员必须决定;-)

以上是关于是否可以通过委托给结构成员来创建实现 Ord 的宏?的主要内容,如果未能解决你的问题,请参考以下文章

C# 怎样用反射创建的对象的给成员委托实例注册方法

C# 使用成员函数的委托来创建新线程

复杂的宏的分析

Swift使用委托实现多个协议

在Golang里如何实现结构体成员指针到结构体自身指针的转换

在 Eclipse 中快速实现包装(委托方法)?