Derive question

If I use derive -- say for example:

#[derive(Hash, Eq, PartialEq, Ord, PartialOrd)]

...is there some way to see what it does behind the scenes?

More detail, only if you need it:
The reason I ask is that the example above works great to make very simple structs comparable and thus sortable, but I would need to write code manually for more complex structs. Especially with structs for which I might want to exclude several elements from the comparison. I have no clue what the prefix "Partial" could be hinting at, to mention another issue. So despite searching for guidelines, my efforts to roll my own are not going well. It would greatly help to see what the derive "decorator" (forgive my Pythonic thinking) is generating under the covers. As usual, thanks for any help!

You can use https://crates.io/crates/cargo-expand to see what the expanded code looks like.

3 Likes

The #[derive(...)] is used to automatically create trait implementations for the struct for the given Traits. For example, #[derive(PartialEq)] will create an implementation of the std::cmp::PartialEq trait. You should read the documentation for that trait on how to manually implement that trait: https://doc.rust-lang.org/std/cmp/trait.PartialEq.html

I'd like to add the fact that cargo expand expands all macros.
In my experience that isn't what I want, because it can become difficult to track down which macros define what code. For derive macro's on their own it might seem simple enough, but add an attribute macro to the derive macro on that type definition and you'll have one hell of a time.

@gbutler69 as someone who's currently writing a derive macro, they can generate way more code than only an impl for the named trait. In my case for example there's some additional types that need to be generated, and trait impls for those types to satisfy trait bounds.

That said, for the builtin derives (PartialEq, Eq, Debug, Clone etc) I expect they'll expand to only an impl block for the trait/type pair.

1 Like

Thanks for all the help! I did get to see the code I was looking for. It seems to use some unusual syntax that I'm not familiar with:

impl ::core::cmp::PartialEq for Person {
    #[inline]
    fn eq(&self, other: &Person) -> bool {
        match *other {
            Person {
                height: ref __self_1_0,
                weight: ref __self_1_1,
            } => match *self {
                Person {
                    height: ref __self_0_0,
                    weight: ref __self_0_1,
                } => (*__self_0_0) == (*__self_1_0) && (*__self_0_1) == (*__self_1_1),
            },
        }
    }

I guess the reason 4 elements are mentioned, with odd names like __self_0_1, is because it's comparing 2 "self" elements to 2 "other" elements (my struct only has height and weight, nothing else).

My original goal was to take a struct with perhaps 8 elements and sort on, let's say, elements #1, #3, #5 and #7, ignoring the other 4.

This might be enough for me to go on; I think I can figure out how to do it. Thanks again.

If you are handwriting it, I would avoid variable names like "___self_1_0". Instead use more meaningful names. If handwritten, the above should be something like:

impl ::core::cmp::PartialEq for Person {
    #[inline]
    fn eq(&self, other: &Person) -> bool {
        match *other {
            Person {
                height: ref other_height,
                weight: ref other_weight,
            } => match *self {
                Person {
                    height: ref self_height,
                    weight: ref self_weight,
                } => (*other_height) == (*self_height) && (*other_weight) == (*self_weight),
            },
        }
    }

Also, when handwriting, I'm not sure that using match like this is actually useful. I would probably handwrite this as:

impl ::core::cmp::PartialEq for Person {
    #[inline]
    fn eq(&self, other: &Person) -> bool {
       self.height == other.height && self.weight == other.weight
    }
2 Likes

I personally made sure that the documentation for every derivable trait had a "How Can I Implement [trait]" section, such as this section for PartialEq.

3 Likes

carols10cents: Yes, I did see your very helpful documentation and using that, equivalency wasn't difficult: just "and" together multiple struct elements and you're fine, you're done. What was difficult was partial_cmp.

With 2 or more elements, should I "and" multiple partial_cmp's together? "Or" them together? That didn't seem right. I tried what seemed most logical, using an if-else structure. I first checked for equality and if not equal, then did the partial_cmp, nesting them in the desired sort sequence. But anyway you don't need the boring details. Suffice it to say I was not clever enough to get partial_cmp working until I saw how "derive" compares multiple elements.

Thank you very much for all the effort put into the excellent documentation.

If your comparisons are cheap (so LLVM can easily optimize out the unneeded ones), then you might be interested in https://doc.rust-lang.org/std/cmp/enum.Ordering.html#method.then

Like self.height.cmp(&other.height).then(self.weight.cmp(&other.weight)) is fine for simple things like i32s.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.