| Crates.io | partial-cmp-derive |
| lib.rs | partial-cmp-derive |
| version | 0.3.1 |
| created_at | 2025-12-06 21:33:12.224641+00 |
| updated_at | 2025-12-10 17:33:48.334417+00 |
| description | Derive PartialEq, Eq, PartialOrd, Ord, and Hash with fine-grained control over field comparison |
| homepage | |
| repository | https://github.com/exotik850/partial-cmp-derive |
| max_upload_size | |
| id | 1970799 |
| size | 89,599 |
A procedural macro for deriving PartialEq, Eq, PartialOrd, Ord, and Hash with fine-grained control over field comparison and hashing behavior.
Hash implementation respects the same field configuration as Eq, ensuring a == b -> hash(a) == hash(b)Eq, Ord, and HashNone sorts first or last[dependencies]
partial-cmp-derive = "0.3"
use partial_cmp_derive::PartialCmp;
// Generates PartialEq, Eq, PartialOrd, Ord, and Hash
#[derive(Debug, PartialCmp)]
struct Player {
#[ord(skip)] // Ignored in all comparisons and hashing
id: u64,
#[ord(order = "desc")] // Higher scores come first
score: u32,
name: String, // Compared in ascending order (default)
}
let alice = Player { id: 1, score: 100, name: "Alice".into() };
let bob = Player { id: 2, score: 100, name: "Bob".into() };
// id is ignored, score compared first (desc), then name (asc)
assert!(alice < bob); // Same score, Alice < Bob alphabetically
// Equality also ignores id
let alice2 = Player { id: 999, score: 100, name: "Alice".into() };
assert_eq!(alice, alice2); // Same score and name, different id - equal!
| Attribute | Description |
|---|---|
#[ord(reverse)] |
Reverse the final comparison result |
#[ord(by = [field1(asc), field2(desc)])] |
Explicit field comparison order |
#[ord(skip_partial_eq)] |
Don't generate PartialEq (implies no other traits) |
#[ord(skip_eq)] |
Don't generate Eq (also disables Ord and Hash) |
#[ord(skip_partial_ord)] |
Don't generate PartialOrd (also disables Ord) |
#[ord(skip_ord)] |
Don't generate Ord |
#[ord(skip_hash)] |
Don't generate Hash |
| Attribute | Description |
|---|---|
#[ord(skip)] |
Exclude from all comparisons and hashing |
#[ord(order = "asc"|"desc")] |
Sort direction (default: asc) |
#[ord(priority = N)] |
Comparison priority (lower = compared first) |
#[ord(key = "path::to::fn")] |
Key extraction function fn(&T) -> U |
#[ord(none_order = "first"|"last")] |
Where None sorts for Option fields |
| Attribute | Description |
|---|---|
#[ord(rank = N)] |
Variant ranking (lower = less than) |
Fields marked with #[ord(skip)] are excluded from equality, ordering, and hash computations:
use partial_cmp_derive::PartialCmp;
#[derive(Debug, PartialCmp)]
struct Record {
#[ord(skip)]
internal_id: u64, // Ignored in eq, cmp, and hash
value: i32,
}
let a = Record { internal_id: 1, value: 10 };
let b = Record { internal_id: 2, value: 10 };
assert_eq!(a, b); // Equal because only value is compared
Use by to specify exactly which fields participate in comparison:
use partial_cmp_derive::PartialCmp;
#[derive(Debug, PartialCmp)]
#[ord(by = [priority(desc), created_at(asc)])]
struct Task {
id: u64, // Not compared or hashed
name: String, // Not compared or hashed
priority: u8,
created_at: u64,
}
The key attribute allows you to specify a function that extracts a comparable value from a field. This single function is used for Eq, Ord, and Hash, ensuring consistency automatically:
use partial_cmp_derive::PartialCmp;
fn abs_key(v: &i32) -> i32 {
v.abs()
}
#[derive(Debug, PartialCmp)]
struct AbsValue {
#[ord(key = "abs_key")]
value: i32,
}
let a = AbsValue { value: -5 };
let b = AbsValue { value: 5 };
assert_eq!(a, b); // Equal because abs(-5) == abs(5)
// And their hashes are also equal, maintaining the Hash/Eq invariant
The key function signature should be fn(&T) -> U where U: Ord + Hash.
use partial_cmp_derive::PartialCmp;
#[derive(Debug, PartialCmp)]
struct MaybeValue {
#[ord(none_order = "first")]
value: Option<i32>,
}
let none = MaybeValue { value: None };
let some = MaybeValue { value: Some(1) };
assert!(none < some); // None comes first
use partial_cmp_derive::PartialCmp;
#[derive(Debug, PartialCmp)]
enum Priority {
#[ord(rank = 0)]
High,
#[ord(rank = 1)]
Medium,
#[ord(rank = 2)]
Low,
}
assert!(Priority::High < Priority::Medium);
assert!(Priority::Medium < Priority::Low);
For types like f32 that don't implement Ord or Hash, you can use a key function to convert them, or skip the relevant traits:
use partial_cmp_derive::PartialCmp;
// Convert f32 to ordered bits for comparison
fn f32_key(v: &f32) -> i32 {
let bits = v.to_bits() as i32;
if bits < 0 { !bits } else { bits }
}
#[derive(Debug, PartialCmp)]
struct FloatWrapper {
#[ord(key = "f32_key")]
value: f32,
}
Or skip traits that aren't needed:
use partial_cmp_derive::PartialCmp;
#[derive(Debug, PartialCmp)]
#[ord(skip_eq, skip_ord, skip_hash)] // Only generate PartialEq and PartialOrd
struct PartialFloat {
value: f32, // Uses default PartialEq/PartialOrd
}
The trait generation respects the following dependencies:
Ord requires Eq and PartialOrdEq and PartialOrd require PartialEqHash is independent but should be consistent with EqWhen you skip a trait, dependent traits are automatically skipped:
| Skip Flag | Traits Generated |
|---|---|
| (none) | PartialEq, Eq, PartialOrd, Ord, Hash |
skip_ord |
PartialEq, Eq, PartialOrd, Hash |
skip_eq |
PartialEq, PartialOrd |
skip_partial_ord |
PartialEq, Eq, Hash |
skip_hash |
PartialEq, Eq, PartialOrd, Ord |
skip_eq, skip_ord |
PartialEq, PartialOrd |
skip_partial_eq |
(none) |
MIT