address_cmp/lib.rs
1//! Easy Address Comparison
2//!
3//! A set of macros to allow your types to be compared based on where they are stored in memory. This is useful when two instances of a type should not be considered equal unless they are literally the same instance.
4//!
5//! With this crate, you can derive `AddressEq`, `AddressOrd`, or `AddressHash` depending on your needs.
6//!
7//! ## Usage
8//!
9//! ```rust
10//! use address_cmp::AddressEq;
11//!
12//! #[derive(AddressEq, Debug)]
13//! struct A {
14//! pub a: u8,
15//! }
16//!
17//! let a1 = A { a: 0 };
18//! let a2 = A { a: 0 };
19//!
20//! assert_ne!(a1, a2);
21//! assert_eq!(a1, a1);
22//! ```
23
24#[cfg(doc)]
25use std::cmp::{Eq, Ord, PartialEq, PartialOrd};
26#[cfg(doc)]
27use std::hash::Hash;
28
29use proc_macro::TokenStream;
30use quote::quote;
31use syn::{parse_macro_input, DeriveInput};
32
33/// Derives [`PartialEq`] and [`Eq`] based on memory addresses.
34///
35/// By deriving [`AddressEq`], the [`PartialEq`] and [`Eq`] traits will automatically be
36/// implemented for the given type. The implementations will use a given instance's address
37/// in memory in when checking equality.
38/// ```rust
39/// use address_cmp::AddressEq;
40///
41/// #[derive(AddressEq, Debug)]
42/// struct Person {
43/// pub age: u8,
44/// pub name: String,
45/// }
46///
47/// let p1 = Person { age: 22, name: "Mercutio".into() };
48/// let p2 = Person { age: 22, name: "Mercutio".into() };
49///
50/// assert_ne!(p1, p2);
51/// assert_eq!(p1, p1);
52/// ```
53#[proc_macro_derive(AddressEq)]
54pub fn address_eq(input: TokenStream) -> TokenStream {
55 let input = parse_macro_input!(input as DeriveInput);
56 let name = &input.ident;
57 let lifetime = &input.generics;
58 (quote! {
59 #[automatically_derived]
60 impl #lifetime ::std::cmp::PartialEq for #name #lifetime {
61 fn eq(&self, other: &Self) -> bool {
62 ::std::ptr::eq(self as *const #name, other as *const #name)
63 }
64 }
65
66 #[automatically_derived]
67 impl #lifetime ::std::cmp::Eq for #name #lifetime {}
68 })
69 .into()
70}
71
72/// Derives [`Hash`] based on memory addresses.
73///
74/// By deriving [`AddressHash`], the [`Hash`] trait will automatically be
75/// implemented for the given type. The implementation will use a given instance's address
76/// in memory in when performing hashing.
77///
78/// Note that since implementations of Hash and Eq must agree, it is recommended to derive both
79/// [`AddressHash`] and [`AddressEq`] together.
80/// ```rust
81/// use address_cmp::{AddressEq, AddressHash};
82/// use std::collections::HashSet;
83///
84/// #[derive(AddressHash, AddressEq)]
85/// enum Animal {
86/// Bat,
87/// Cat,
88/// }
89///
90/// let mut set = HashSet::new();
91/// let a1 = Animal::Bat;
92/// let a2 = Animal::Cat;
93/// let a3 = Animal::Bat;
94///
95/// set.insert(a1);
96/// set.insert(a2);
97/// set.insert(a3);
98///
99/// assert_eq!(set.len(), 3);
100/// ```
101#[proc_macro_derive(AddressHash)]
102pub fn address_hash(input: TokenStream) -> TokenStream {
103 let input = parse_macro_input!(input as DeriveInput);
104 let name = &input.ident;
105 let lifetime = &input.generics;
106 (quote! {
107 #[automatically_derived]
108 impl #lifetime ::std::hash::Hash for #name #lifetime {
109 fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
110 (self as *const #name).hash(state)
111 }
112 }
113 })
114 .into()
115}
116
117/// Derives [`PartialOrd`] and [`Ord`] based on memory addresses.
118///
119/// By deriving [`AddressOrd`], the [`PartialOrd`] and [`Ord`] traits will automatically be
120/// implemented for the given type. The implementations will use a given instance's address
121/// in memory in when performing comparison.
122///
123/// This form of comparison can be useful in certain cases, like implementing ordering based
124/// on position in a slice, but can also produce unexpected results depending on how the compiler
125/// chooses to place data in memory.
126///
127/// Since implementing [`PartialOrd`] on a type requires that the type also implements [`PartialEq`]
128/// and implementations of Hash and Eq must agree, it is recommended to derive both
129/// [`AddressOrd`] and [`AddressEq`] together.
130/// ```rust
131/// use address_cmp::{AddressEq, AddressOrd};
132///
133/// #[derive(AddressEq, AddressOrd, Debug)]
134/// struct Person {
135/// pub age: u8,
136/// pub name: String,
137/// }
138///
139/// let p1 = Person { age: 22, name: "Mercutio".into() };
140/// let p2 = Person { age: 22, name: "Mercutio".into() };
141/// let p3 = Person { age: 21, name: "Benvolio".into() };
142///
143/// let friends = vec![p1, p2, p3];
144///
145/// assert!(friends[0] < friends[1]);
146/// assert!(friends[1] < friends[2]);
147/// assert_eq!(friends[0], friends[0]);
148/// ```
149#[proc_macro_derive(AddressOrd)]
150pub fn address_ord(input: TokenStream) -> TokenStream {
151 let input = parse_macro_input!(input as DeriveInput);
152 let name = &input.ident;
153 let lifetime = &input.generics;
154 (quote! {
155 #[automatically_derived]
156 impl #lifetime ::std::cmp::Ord for #name #lifetime {
157 fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
158 (self as *const #name).cmp(&(other as *const #name))
159 }
160 }
161
162 #[automatically_derived]
163 impl #lifetime ::std::cmp::PartialOrd for #name #lifetime {
164 fn partial_cmp(&self, other: &Self) -> ::std::option::Option<::std::cmp::Ordering> {
165 ::std::option::Option::Some(self.cmp(other))
166 }
167 }
168 })
169 .into()
170}