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