partial-cmp-derive 0.2.1

Derive PartialEq, Eq, PartialOrd, and Ord with fine-grained control over field comparison
Documentation

partial-cmp-derive

A procedural macro for deriving PartialEq, Eq, PartialOrd, and Ord with fine-grained control over field comparison behavior.

Features

  • Consistent trait generation: All four comparison traits are generated with consistent behavior
  • Skip fields: Exclude fields from all comparisons (both equality and ordering)
  • Custom comparators: Use custom functions for ordering and/or equality
  • Sort order control: Ascending or descending per field
  • Field priority: Control comparison order independent of declaration order
  • Explicit field ordering: Specify exactly which fields to compare and in what order
  • Option handling: Control whether None sorts first or last
  • Enum ranking: Control variant ordering independent of declaration order
  • Trait selection: Opt out of specific traits as needed

Installation

[dependencies]

partial-cmp-derive = "0.2"

Quick Start

use partial_cmp_derive::PartialCmp;

// Generates PartialEq, Eq, PartialOrd, and Ord
#[derive(Debug, PartialCmp)]
struct Player {
    #[ord(skip)]           // Ignored in all comparisons
    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!

Attributes

Struct-Level Attributes

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)
#[ord(skip_partial_ord)] Don't generate PartialOrd (also disables Ord)
#[ord(skip_ord)] Don't generate Ord

Field-Level Attributes

Attribute Description
#[ord(skip)] Exclude from all comparisons
#[ord(order = "asc"|"desc")] Sort direction (default: asc)
#[ord(priority = N)] Comparison priority (lower = compared first)
#[ord(compare_with = "path::to::fn")] Custom comparison function fn(&T, &T) -> Ordering
#[ord(eq_with = "path::to::fn")] Custom equality function fn(&T, &T) -> bool
#[ord(none_order = "first"|"last")] Where None sorts for Option fields

Enum Variant Attributes

Attribute Description
#[ord(rank = N)] Variant ranking (lower = less than)

Examples

Skipping Fields

Fields marked with #[ord(skip)] are excluded from both equality and ordering comparisons:

use partial_cmp_derive::PartialCmp;

#[derive(Debug, PartialCmp)]
struct Record {
    #[ord(skip)]
    internal_id: u64,  // Ignored in eq and cmp
    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

Explicit Field Ordering

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
    name: String,      // Not compared
    priority: u8,
    created_at: u64,
}

Custom Comparison Functions

use partial_cmp_derive::PartialCmp;
use std::cmp::Ordering;

fn cmp_abs(a: &i32, b: &i32) -> Ordering {
    a.abs().cmp(&b.abs())
}

fn eq_abs(a: &i32, b: &i32) -> bool {
    a.abs() == b.abs()
}

#[derive(Debug, PartialCmp)]
struct AbsValue {
    #[ord(compare_with = "cmp_abs", eq_with = "eq_abs")]
    value: i32,
}

let a = AbsValue { value: -5 };
let b = AbsValue { value: 5 };

assert_eq!(a, b);  // Equal because |-5| == |5|

Option Handling

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

Enum Ranking

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);

Working with Non-Ord Types

For types like f32 that don't implement Ord, use skip_ord and skip_eq:

use partial_cmp_derive::PartialCmp;
use std::cmp::Ordering;

fn cmp_f32(a: &f32, b: &f32) -> Ordering {
    a.partial_cmp(b).unwrap_or(Ordering::Equal)
}

#[derive(Debug, PartialCmp)]
#[ord(skip_eq, skip_ord)]
struct FloatWrapper {
    #[ord(compare_with = "cmp_f32")]
    value: f32,
}

Trait Dependencies

The trait generation respects the following dependencies:

  • Ord requires Eq and PartialOrd
  • Eq and PartialOrd require PartialEq

When you skip a trait, dependent traits are automatically skipped:

Skip Flag Traits Generated
(none) PartialEq, Eq, PartialOrd, Ord
skip_ord PartialEq, Eq, PartialOrd
skip_eq PartialEq, PartialOrd
skip_partial_ord PartialEq, Eq
skip_eq, skip_ord PartialEq, PartialOrd
skip_partial_eq (none)

License

MIT