debug_tag/lib.rs
1
2//! A library for creating debug-only tags to track and check where values are used.
3//!
4//! Useful for asserting that two values stem from the same origin. For example, an operation on two
5//! nodes may only be valid if the nodes are in the same graph. `DebugTag` can be used to check for
6//! this in tests and when debugging.
7//!
8//! See the `DebugTag` type for more details.
9//!
10//! # Motivation
11//!
12//! When developing Rust, we often use *indices* to refer to other values instead of using
13//! *pointers*. Using indices allows us to "point" to a value without borrowing it and can basically
14//! be seen as bypassing the borrow checker in a restricted way.
15//!
16//! One of the issues of using indicies, is that it is not connected at compile time to the
17//! container storing the value. An index used with an incorrect container might panic, or worse,
18//! return garbage values.
19//!
20//! We need a way to "tag" values such that we can determine if an index is used with the correct
21//! container.
22//!
23//! # Example
24//!
25//! ```
26//! use debug_tag::DebugTag;
27//!
28//! /// An example vec-like structure that allows pushing values and getting values by index.
29//! #[derive(Default)]
30//! struct Slab<T>{
31//! items: Vec<T>,
32//! tag: DebugTag,
33//! }
34//!
35//! /// An index into a value on a slab.
36//! #[derive(Copy, Clone)]
37//! struct Index {
38//! index: usize,
39//! tag: DebugTag,
40//! }
41//!
42//! impl<T> Slab<T> {
43//! /// Pushes a new value onto the slab.
44//! fn push(&mut self, item: T) -> Index {
45//! let index = self.items.len();
46//! self.items.push(item);
47//! Index {
48//! index,
49//! tag: self.tag
50//! }
51//! }
52//!
53//! /// Gets a value in this `Slab`. If the index is not from this slab, either panics or
54//! /// returns an arbitrary value.
55//! fn get(&self, index: Index) -> &T {
56//! assert_eq!(self.tag, index.tag, "Index must stem from this slab");
57//! &self.items[index.index]
58//! }
59//! }
60//!
61//! let mut slab_a = Slab::default();
62//! let ix = slab_a.push(42);
63//!
64//! assert_eq!(slab_a.get(ix), &42);
65//!
66//! let mut slab_b = Slab::default();
67//! slab_b.push(1337);
68//!
69//! // Panics due to the tags being checked:
70//! // assert_eq!(slab_b.get(ix), &1337);
71//! ```
72//!
73//! Without `DebugTag`s, the last line above would just return 1337 - a "garbage value" since `ix`
74//! stems from a different `Slab`.
75
76#[cfg(debug_assertions)]
77mod checked {
78 use std::cell::Cell;
79 use std::sync::atomic::{AtomicU32, Ordering};
80
81 // The increment to global every time we fetch a local tag offset. This is equal to
82 // 2**32 * (1 - 1/(golden ratio)), which ends up distributing offsets well for an arbitrary
83 // number of local threads.
84 const INCREMENT: u32 = 1_640_531_527;
85
86 static GLOBAL: AtomicU32 = AtomicU32::new(INCREMENT);
87
88 thread_local! {
89 static LOCAL: Cell<u32> = Cell::new(GLOBAL.fetch_add(INCREMENT, Ordering::SeqCst));
90 }
91
92 pub fn next() -> u32 {
93 LOCAL.with(|local| {
94 let old = local.get();
95 local.set(old.wrapping_add(1));
96 old
97 })
98 }
99}
100
101/// A value that guarentees that if two `DebugTag`s are not equal, then they are *not* clones.
102///
103/// This can be used to tag information during debug, such that the use of a value can be tracked
104/// and checked. For example, you can use this to ensure (in debug) that a value returned by a data
105/// structure is only used with the instance that returned the value.
106///
107/// This tagging is only done if `debug_assertions` is set. If `debug_assertions` is not set, then
108/// all `DebugTags` are equal. Even if `debug_assertions` is set, two `DebugTag`s that are not
109/// clones can still be equal. This is unlikely, however.
110///
111/// Therefore, functionality should not directly depend on the equality these tags but only use them
112/// for additional sanity checks.
113#[derive(Clone, Copy, PartialEq, Eq, Debug)]
114#[non_exhaustive]
115pub struct DebugTag(
116 #[cfg(debug_assertions)]
117 u32,
118);
119
120impl Default for DebugTag {
121 fn default() -> DebugTag {
122 #[cfg(debug_assertions)]
123 let tag = DebugTag(checked::next());
124
125 #[cfg(not(debug_assertions))]
126 let tag = DebugTag();
127
128 tag
129 }
130}
131
132impl DebugTag {
133 /// Creates a new `DebugTag`
134 pub fn new() -> DebugTag {
135 DebugTag::default()
136 }
137
138 /// Create a new tag with the specified value.
139 ///
140 /// Prefer using `new` instead, which will generate a value. Use this only in cases where that
141 /// is not possible, like when creating a const debug tag.
142 ///
143 /// The tag value should be a randomly chosen constant.
144 pub const fn from(_tag: u32) -> DebugTag {
145 #[cfg(debug_assertions)]
146 let tag = DebugTag(_tag);
147
148 #[cfg(not(debug_assertions))]
149 let tag = DebugTag();
150
151 tag
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::DebugTag;
158
159 #[test]
160 #[cfg(debug_assertions)]
161 fn not_equal() {
162 let a = DebugTag::new();
163 let b = DebugTag::new();
164 assert!(a != b);
165 }
166
167 #[test]
168 fn equal() {
169 let a = DebugTag::new();
170 let b = a.clone();
171 assert!(a == b);
172 }
173
174 #[test]
175 fn reflexive() {
176 let a = DebugTag::new();
177 assert!(a == a);
178 }
179}