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}