Crate cmp_by_derive

source ·
Expand description

GitHub Crates.io docs.rs Continuous integration

cmp_by_derive

This crate provides the CmpBy and HashBy derive macros.

  • CmpBy derives the traits Ord, PartialOrd, Eq and PartialEq on types that can’t automatically derive those traits because they contain unorderable fields such as f32 by selecting fields to use in the comparison.
  • CmpBy and HashBy can also implement their traits by calling arbitrary methods

Usage

Fields that should be used for sorting are marked with the attribute #[cmp_by]. Other fields will be ignored.

This saves a lot of boilerplate, as you can see with the SomethingElse struct.

use std::cmp::Ordering;
use cmp_by_derive::CmpBy;

#[derive(CmpBy)]
struct Something {
    #[cmp_by]
    a: u16,
    #[cmp_by]
    b: u16,
    c: f32,
}

struct SomethingElse {
    a: u16,
    b: u16,
    c: f32,
}

impl Eq for SomethingElse {}

impl PartialEq<Self> for SomethingElse {
    fn eq(&self, other: &Self) -> bool {
        self.cmp(other) == Ordering::Equal
    }
}

impl PartialOrd<Self> for SomethingElse {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for SomethingElse {
    fn cmp(&self, other: &Self) -> Ordering {
        self.a.cmp(&other.a).then_with(|| { self.b.cmp(&other.b) })
    }
}



assert_eq!(Something { a: 2, b: 0, c: 0.2 }.cmp(&Something { a: 1, b: 1, c: 1.3 }),
           SomethingElse { a: 2, b: 0, c: 0.2 }.cmp(&SomethingElse { a: 1, b: 1, c: 1.3 }));
assert_eq!(Something { a: 1, b: 0, c: 3.3 }.cmp(&Something { a: 1, b: 1, c: 2.3 }),
           SomethingElse { a: 1, b: 0, c: 3.3 }.cmp(&SomethingElse { a: 1, b: 1, c: 2.3 }));

You can use HashBy the same way you would use CmpBy:

use cmp_by_derive::HashBy;
use cmp_by_derive::CmpBy;
use std::collections::hash_set::HashSet;

#[derive(HashBy, CmpBy)]
struct Something {
    #[cmp_by]
    #[hash_by]
    a: u16,
    #[cmp_by]
    #[hash_by]
    b: u16,
    c: f32,
}

let mut set = HashSet::new();
let something = Something { a: 2, b: 0, c: 0.2 };
assert!(set.insert(something));

All together

Imagine the following :

#[derive(Ord, PartialOrd, Eq, PartialEq)]
struct Midi {
    global_time: usize,
    note: Note,
}

#[derive(CmpBy, Debug)]
#[cmp_by(channel(), pitch(), _fields)]
enum Note {
// ...
}

impl Note {
    fn channel(&self) -> Option<&u8> {
    }

    fn pitch(&self) -> Option<&u8> {
    }
}

assert_eq!(
    Midi {
        global_time: 0,
        note: Note::NoteOn {
            pitch: 0,
            channel: 0,
        }
    }
        .cmp(&Midi {
            global_time: 0,
            note: Note::NoteOn {
                pitch: 0,
                channel: 0,
            }
        }),
    Ordering::Equal
);
assert_eq!(
    Midi {
        global_time: 0,
        note: Note::NoteOn {
            pitch: 2,
            channel: 2,
        }
    }
        .cmp(&Midi {
            global_time: 2,
            note: Note::NoteOff {
                pitch: 0,
                channel: 0,
            }
        }),
    Ordering::Less
);
assert_eq!(
    Midi {
        global_time: 0,
        note: Note::NoteOn {
            pitch: 2,
            channel: 0,
        }
    }
        .cmp(&Midi {
            global_time: 0,
            note: Note::NoteOff {
                pitch: 0,
                channel: 2,
            }
        }),
    Ordering::Less
);
assert_eq!(
    Midi {
        global_time: 0,
        note: Note::NoteOn {
            pitch: 0,
            channel: 0,
        }
    }
        .cmp(&Midi {
            global_time: 0,
            note: Note::NoteOff {
                pitch: 0,
                channel: 2,
            }
        }),
    Ordering::Less
);
assert_eq!(
    Midi {
        global_time: 0,
        note: Note::NoteOn {
            pitch: 0,
            channel: 0,
        }
    }
        .cmp(&Midi {
            global_time: 0,
            note: Note::NoteOff {
                pitch: 0,
                channel: 0,
            }
        }),
    Ordering::Less
);

Now I have a Note enum that will cmp by global_time, channel, pitch, and lastly by variant order ( enum_sequence ). Note that None is always less than Some.

Conversely, separate structs such as NoteOn may derive from CmpBy in order to ignore some fields ( ex: velocity may be a f32, so we can’t directly derive Ord ).

Derive Macros

  • Fields that should be used for comparing are marked with the attribute #[cmp_by]. Other fields will be ignored.
  • Fields that should be used for hashing are marked with the attribute #[hash_by]. Other fields will be ignored.