Is it possible to create a macro that implements Ord by delegating to a struct member?

╄→гoц情女王★ 提交于 2019-12-12 15:50:27

问题


I have a struct:

struct Student {
    first_name: String,
    last_name: String,
}

I want to create a Vec<Student> that can be sorted by last_name. I need to implement Ord, PartialOrd and 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
    }
}

This can be quite monotonous and repetitive if you have a lot of structs with an obvious field to sort by. Is it possible to create a macro to automatically implement this?

Something like:

impl_ord!(Student, Student.last_name)

I found Automatically implement traits of enclosed type for Rust newtypes (tuple structs with one field), but it's not quite what I'm looking for.


回答1:


Yes, you can, but first: please read why you shouldn't!


Why not?

When a type implements Ord or PartialOrd it means that this type has a natural ordering, which in turn means that the ordering implemented is the only logical one. Take integers: 3 is naturally smaller than 4. There are other useful orderings, for sure. You could sort integers in decreasing order instead by using a reversed ordering, but there is only one natural one.

Now you have a type consisting of two strings. Is there a natural ordering? I claim: no! There are a lot of useful orderings, but is ordering by the last name more natural than ordering by the first name? I don't think so.

How to do it then?

There are two other sort methods:

  • sort_by(), and
  • sort_by_key().

Both let you modify the way the sorting algorithm compares value. Sorting by the last name can be done like this (full code):

students.sort_by(|a, b| a.last_name.cmp(&b.last_name));

This way, you can specify how to sort on each method call. Sometimes you might want to sort by last name and other times you want to sort by first name. Since there is no obvious and natural way to sort, you shouldn't "attach" any specific way of sorting to the type itself.

But seriously, I want a macro...

Of course, it is possible in Rust to write such a macro. It's actually quite easy once you understand the macro system. But let's not do it for your Student example, because -- as I hope you understand by now -- it's a bad idea.

When is it a good idea? When only one field semantically is part of the type. Take this data structure:

struct Foo {
    actual_data: String,
    _internal_cache: String,
}

Here, the _internal_cache does not semantically belong to your type. It's just an implementation detail and thus should be ignored for Eq and Ord. The simple macro is:

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 {}
    }
}

Why do I call such a big chunk of code simple you ask? Well, the vast majority of this code is just exactly what you have already written: the impls. I performed two simple steps:

  1. Add the macro definition around your code and think about what parameters we need (type_name and field)
  2. Replace all your mentions of Student with $type_name and all your mentions of last_name with $field

That's why it's called "macro by example": you basically just write your normal code as an example, but can make parts of it variable per parameter.

You can test the whole thing here.



来源:https://stackoverflow.com/questions/45664392/is-it-possible-to-create-a-macro-that-implements-ord-by-delegating-to-a-struct-m

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!