Skip to main content

context_trait/
ord_ctx.rs

1//! [`OrdContext`]: custom ordering via function pointer.
2
3use crate::WithContext;
4use std::cmp::Ordering;
5
6/// A context providing a custom comparison function.
7///
8/// When used with [`WithContext`], implements `PartialEq`, `Eq`,
9/// `PartialOrd`, and `Ord` by dispatching through the stored comparator.
10///
11/// # Examples
12///
13/// ```
14/// use context_trait::{WithContext, OrdContext};
15///
16/// // Sort in reverse order
17/// let ctx = OrdContext { compare: |a: &i32, b: &i32| b.cmp(a) };
18/// let mut items: Vec<_> = [3, 1, 2].iter()
19///     .map(|&v| WithContext { inner: v, ctx })
20///     .collect();
21/// items.sort();
22/// let values: Vec<i32> = items.into_iter().map(|w| w.inner).collect();
23/// assert_eq!(values, vec![3, 2, 1]);
24/// ```
25pub struct OrdContext<T> {
26    /// The comparison function.
27    pub compare: fn(&T, &T) -> Ordering,
28}
29
30// Manual impls: the fn pointer is always Copy regardless of T.
31impl<T> Clone for OrdContext<T> {
32    fn clone(&self) -> Self {
33        *self
34    }
35}
36impl<T> Copy for OrdContext<T> {}
37impl<T> std::fmt::Debug for OrdContext<T> {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        f.debug_struct("OrdContext")
40            .field("compare", &(self.compare as usize))
41            .finish()
42    }
43}
44
45impl<T> PartialEq for WithContext<T, OrdContext<T>> {
46    fn eq(&self, other: &Self) -> bool {
47        (self.ctx.compare)(&self.inner, &other.inner) == Ordering::Equal
48    }
49}
50
51impl<T> Eq for WithContext<T, OrdContext<T>> {}
52
53impl<T> PartialOrd for WithContext<T, OrdContext<T>> {
54    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
55        Some(self.cmp(other))
56    }
57}
58
59impl<T> Ord for WithContext<T, OrdContext<T>> {
60    fn cmp(&self, other: &Self) -> Ordering {
61        (self.ctx.compare)(&self.inner, &other.inner)
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn sort_with_custom_ord() {
71        let ctx = OrdContext {
72            compare: |a: &i32, b: &i32| b.cmp(a),
73        };
74        let mut items: Vec<_> = [3, 1, 4, 1, 5]
75            .iter()
76            .map(|&v| WithContext { inner: v, ctx })
77            .collect();
78        items.sort();
79        let values: Vec<i32> = items.into_iter().map(|w| w.inner).collect();
80        assert_eq!(values, vec![5, 4, 3, 1, 1]);
81    }
82
83    #[test]
84    fn equality_through_context() {
85        let ctx = OrdContext {
86            compare: |a: &(i32, i32), b: &(i32, i32)| a.0.cmp(&b.0),
87        };
88        let a = WithContext {
89            inner: (1, 100),
90            ctx,
91        };
92        let b = WithContext {
93            inner: (1, 200),
94            ctx,
95        };
96        // Equal by first element only
97        assert_eq!(a, b);
98    }
99
100    #[test]
101    fn btreeset_with_custom_ord() {
102        use std::collections::BTreeSet;
103
104        let ctx = OrdContext {
105            compare: |a: &String, b: &String| a.len().cmp(&b.len()),
106        };
107
108        let mut set = BTreeSet::new();
109        set.insert(WithContext {
110            inner: "hello".to_string(),
111            ctx,
112        });
113        set.insert(WithContext {
114            inner: "hi".to_string(),
115            ctx,
116        });
117        set.insert(WithContext {
118            inner: "world".to_string(),
119            ctx,
120        }); // same len as "hello"
121
122        // "world" collides with "hello" (same length), so only 2 entries
123        assert_eq!(set.len(), 2);
124    }
125}