1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//! Swap out a type's `Ord`, `Hash`, `Display` (or any user-defined trait)
//! for one block of code, without newtype boilerplate.
//!
//! Sometimes you want to sort a `Vec<i32>` descending, hash a `String`
//! case-insensitively, or render a number with a custom prefix, but only
//! for one operation. The orthodox Rust answer is to wrap the value in a
//! newtype that implements the trait differently. That works, but it
//! requires a new struct, manual impls, and you lose access to all the
//! existing impls on the inner type.
//!
//! This crate offers a lighter alternative. [`WithContext<T, Ctx>`] is a
//! wrapper that pairs a value with a *context*, a small `Copy` struct of
//! function pointers that supplies the relevant trait implementation.
//! Standard library APIs that take `Ord` / `Hash` / `Display` work
//! unchanged; the context decides the behavior.
//!
//! Three built-in contexts cover the common cases:
//!
//! - [`OrdContext<T>`] supplies a custom comparator.
//! - [`HashContext<T>`] supplies a custom hasher.
//! - [`DisplayContext<T>`] supplies a custom formatter.
//!
//! And three corresponding macros wrap up the lift / call / project dance:
//!
//! - [`with_ord!`]
//! - [`with_hash!`]
//! - [`with_display!`]
//!
//! For traits beyond these three, [`impl_context_trait!`] generates a new
//! context type for an arbitrary trait of yours.
//!
//! # Why function pointers?
//!
//! Contexts hold `fn` pointers, not `Box<dyn Fn>`. This makes the wrapper
//! `Copy` regardless of `T`, which is what lets `BTreeSet`,
//! `slice::sort`, and `HashMap` accept it without complaint. It also
//! means the comparator must be stateless. If you need captured state,
//! reach for a newtype.
//!
//! See [`docs/phase3-context-trait.md`][phase3] for the full design
//! rationale.
//!
//! [phase3]: https://github.com/joshburgess/reify-reflect/blob/main/docs/phase3-context-trait.md
//!
//! # Examples
//!
//! Sort a slice descending without a newtype:
//!
//! ```
//! use context_trait::{with_ord, OrdContext, WithContext};
//!
//! let items = vec![3i32, 1, 4, 1, 5];
//! with_ord!(items, |a: &i32, b: &i32| b.cmp(a),
//! |wrapped: &[WithContext<i32, OrdContext<i32>>]| {
//! let mut sorted = wrapped.to_vec();
//! sorted.sort(); // uses the descending comparator
//! let values: Vec<i32> = sorted.into_iter().map(|w| w.inner).collect();
//! assert_eq!(values, vec![5, 4, 3, 1, 1]);
//! });
//! ```
pub use WithContext;
pub use DisplayContext;
pub use HashContext;
pub use OrdContext;