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}