borrowscope_runtime/tracker/core.rs
1//! Core ownership tracking: new, borrow, move, drop
2
3use super::TRACKER;
4
5///
6/// # Arguments
7///
8/// * `name` - A descriptive name for the variable (used in event output)
9/// * `value` - The value being tracked (returned unchanged)
10///
11/// # Returns
12///
13/// The input `value`, unchanged. This allows chaining:
14/// ```rust
15/// # use borrowscope_runtime::*;
16/// # reset();
17/// let x = track_new("x", 42);
18/// assert_eq!(x, 42);
19/// ```
20///
21/// # Examples
22///
23/// Basic usage:
24/// ```rust
25/// # use borrowscope_runtime::*;
26/// # reset();
27/// let data = track_new("data", vec![1, 2, 3]);
28/// let events = get_events();
29/// assert!(events[0].is_new());
30/// ```
31///
32/// With structs:
33/// ```rust
34/// # use borrowscope_runtime::*;
35/// # reset();
36/// struct Point { x: i32, y: i32 }
37/// let p = track_new("point", Point { x: 10, y: 20 });
38/// ```
39#[inline(always)]
40pub fn track_new<T>(
41 #[cfg_attr(not(feature = "track"), allow(unused_variables))] name: &str,
42 value: T,
43) -> T {
44 #[cfg(feature = "track")]
45 {
46 let type_name = std::any::type_name::<T>();
47 let mut tracker = TRACKER.lock();
48 tracker.record_new(name, type_name);
49 }
50 value
51}
52
53/// Track an immutable borrow.
54///
55/// Records a `Borrow` event with `mutable: false` and returns the reference unchanged.
56/// Use this when creating a shared reference (`&T`).
57///
58/// # Arguments
59///
60/// * `name` - A descriptive name for the borrow
61/// * `value` - The reference being tracked (returned unchanged)
62///
63/// # Returns
64///
65/// The input reference, unchanged.
66///
67/// # Examples
68///
69/// ```rust
70/// # use borrowscope_runtime::*;
71/// # reset();
72/// let data = track_new("data", vec![1, 2, 3]);
73/// let r1 = track_borrow("r1", &data);
74/// let r2 = track_borrow("r2", &data); // Multiple immutable borrows OK
75/// println!("{:?}, {:?}", r1, r2);
76///
77/// let events = get_events();
78/// assert!(events[1].is_borrow());
79/// assert!(events[2].is_borrow());
80/// ```
81#[inline(always)]
82pub fn track_borrow<'a, T: ?Sized>(
83 #[cfg_attr(not(feature = "track"), allow(unused_variables))] name: &str,
84 value: &'a T,
85) -> &'a T {
86 #[cfg(feature = "track")]
87 {
88 let mut tracker = TRACKER.lock();
89 tracker.record_borrow(name, "unknown", false);
90 }
91 value
92}
93
94/// Track a mutable borrow.
95///
96/// Records a `Borrow` event with `mutable: true` and returns the reference unchanged.
97/// Use this when creating an exclusive reference (`&mut T`).
98///
99/// # Arguments
100///
101/// * `name` - A descriptive name for the borrow
102/// * `value` - The mutable reference being tracked (returned unchanged)
103///
104/// # Returns
105///
106/// The input mutable reference, unchanged.
107///
108/// # Examples
109///
110/// ```rust
111/// # use borrowscope_runtime::*;
112/// # reset();
113/// let mut data = track_new("data", vec![1, 2, 3]);
114/// {
115/// let r = track_borrow_mut("r", &mut data);
116/// r.push(4);
117/// }
118/// // Mutable borrow ended, can borrow again
119/// let events = get_events();
120/// assert!(events[1].is_borrow());
121/// ```
122#[inline(always)]
123pub fn track_borrow_mut<'a, T: ?Sized>(
124 #[cfg_attr(not(feature = "track"), allow(unused_variables))] name: &str,
125 value: &'a mut T,
126) -> &'a mut T {
127 #[cfg(feature = "track")]
128 {
129 let mut tracker = TRACKER.lock();
130 tracker.record_borrow(name, "unknown", true);
131 }
132 value
133}
134
135/// Track an ownership move.
136///
137/// Records a `Move` event and returns the value unchanged.
138/// Use this when ownership transfers from one variable to another.
139///
140/// # Arguments
141///
142/// * `from_name` - Name of the source variable (giving up ownership)
143/// * `to_name` - Name of the destination variable (receiving ownership)
144/// * `value` - The value being moved (returned unchanged)
145///
146/// # Returns
147///
148/// The input `value`, unchanged.
149///
150/// # Examples
151///
152/// ```rust
153/// # use borrowscope_runtime::*;
154/// # reset();
155/// let s1 = track_new("s1", String::from("hello"));
156/// let s2 = track_move("s1", "s2", s1);
157/// // s1 is no longer valid, s2 owns the String
158///
159/// let events = get_events();
160/// assert!(events[1].is_move());
161/// ```
162#[inline(always)]
163pub fn track_move<T>(
164 #[cfg_attr(not(feature = "track"), allow(unused_variables))] from_name: &str,
165 #[cfg_attr(not(feature = "track"), allow(unused_variables))] to_name: &str,
166 value: T,
167) -> T {
168 #[cfg(feature = "track")]
169 {
170 let mut tracker = TRACKER.lock();
171 tracker.record_move(from_name, to_name);
172 }
173 value
174}
175
176/// Track a variable going out of scope.
177///
178/// Records a `Drop` event. Call this when a variable's lifetime ends.
179/// Unlike other tracking functions, this doesn't return a value since
180/// the variable is being destroyed.
181///
182/// # Arguments
183///
184/// * `name` - Name of the variable being dropped
185///
186/// # Examples
187///
188/// ```rust
189/// # use borrowscope_runtime::*;
190/// # reset();
191/// {
192/// let x = track_new("x", 42);
193/// // x goes out of scope here
194/// track_drop("x");
195/// }
196///
197/// let events = get_events();
198/// assert!(events[1].is_drop());
199/// ```
200///
201/// # Note
202///
203/// For automatic drop tracking, consider using RAII guards or the
204/// future `borrowscope-macro` crate which will instrument drops automatically.
205#[inline(always)]
206pub fn track_drop(#[cfg_attr(not(feature = "track"), allow(unused_variables))] name: &str) {
207 #[cfg(feature = "track")]
208 {
209 let mut tracker = TRACKER.lock();
210 tracker.record_drop(name);
211 }
212}
213
214/// Track multiple drops in batch (optimized).
215///
216/// Records multiple `Drop` events efficiently with a single lock acquisition.
217/// Use this when multiple variables go out of scope simultaneously.
218///
219/// # Arguments
220///
221/// * `names` - Slice of variable names being dropped
222///
223/// # Examples
224///
225/// ```rust
226/// # use borrowscope_runtime::*;
227/// # reset();
228/// let a = track_new("a", 1);
229/// let b = track_new("b", 2);
230/// let c = track_new("c", 3);
231/// // All go out of scope together
232/// track_drop_batch(&["a", "b", "c"]);
233///
234/// let events = get_events();
235/// assert_eq!(events.len(), 6); // 3 New + 3 Drop
236/// ```
237#[inline(always)]
238pub fn track_drop_batch(
239 #[cfg_attr(not(feature = "track"), allow(unused_variables))] names: &[&str],
240) {
241 #[cfg(feature = "track")]
242 {
243 let mut tracker = TRACKER.lock();
244 for &name in names {
245 tracker.record_drop(name);
246 }
247 }
248}
249
250/// Reset tracking state.
251///
252/// Clears all recorded events and resets internal counters.
253/// Call this before starting a new tracking session.
254///
255/// # Examples
256///
257/// ```rust
258/// # use borrowscope_runtime::*;
259/// let _ = track_new("x", 1);
260/// assert!(!get_events().is_empty());
261///
262/// reset();
263/// assert!(get_events().is_empty());
264/// ```
265///
266/// # Thread Safety
267///
268/// This function is thread-safe but will clear events from all threads.
269/// In multi-threaded tests, use synchronization to ensure reset completes
270/// before other threads start tracking.
271pub fn __track_new_with_id_helper<T>(
272 #[cfg_attr(not(feature = "track"), allow(unused_variables))] id: usize,
273 #[cfg_attr(not(feature = "track"), allow(unused_variables))] name: &str,
274 #[cfg_attr(not(feature = "track"), allow(unused_variables))] location: &str,
275 value: T,
276) -> T {
277 #[cfg(feature = "track")]
278 {
279 let type_name = std::any::type_name::<T>();
280 let mut tracker = TRACKER.lock();
281 tracker.record_new_with_id(id, name, type_name, location);
282 }
283 value
284}
285
286/// Track a new variable with explicit ID and location (advanced API)
287#[inline(always)]
288pub fn track_new_with_id<T>(
289 #[cfg_attr(not(feature = "track"), allow(unused_variables))] id: usize,
290 #[cfg_attr(not(feature = "track"), allow(unused_variables))] name: &str,
291 #[cfg_attr(not(feature = "track"), allow(unused_variables))] type_name: &str,
292 #[cfg_attr(not(feature = "track"), allow(unused_variables))] location: &str,
293 value: T,
294) -> T {
295 #[cfg(feature = "track")]
296 {
297 let mut tracker = TRACKER.lock();
298 tracker.record_new_with_id(id, name, type_name, location);
299 }
300 value
301}
302
303/// Track an immutable borrow with full metadata (advanced API)
304#[inline(always)]
305pub fn track_borrow_with_id<'a, T: ?Sized>(
306 #[cfg_attr(not(feature = "track"), allow(unused_variables))] borrower_id: usize,
307 #[cfg_attr(not(feature = "track"), allow(unused_variables))] owner_id: usize,
308 #[cfg_attr(not(feature = "track"), allow(unused_variables))] name: &str,
309 #[cfg_attr(not(feature = "track"), allow(unused_variables))] location: &str,
310 #[cfg_attr(not(feature = "track"), allow(unused_variables))] mutable: bool,
311 value: &'a T,
312) -> &'a T {
313 #[cfg(feature = "track")]
314 {
315 let mut tracker = TRACKER.lock();
316 tracker.record_borrow_with_id(borrower_id, owner_id, name, location, mutable);
317 }
318 value
319}
320
321/// Track a mutable borrow with full metadata (advanced API)
322#[inline(always)]
323pub fn track_borrow_mut_with_id<'a, T: ?Sized>(
324 #[cfg_attr(not(feature = "track"), allow(unused_variables))] borrower_id: usize,
325 #[cfg_attr(not(feature = "track"), allow(unused_variables))] owner_id: usize,
326 #[cfg_attr(not(feature = "track"), allow(unused_variables))] name: &str,
327 #[cfg_attr(not(feature = "track"), allow(unused_variables))] location: &str,
328 value: &'a mut T,
329) -> &'a mut T {
330 #[cfg(feature = "track")]
331 {
332 let mut tracker = TRACKER.lock();
333 tracker.record_borrow_with_id(borrower_id, owner_id, name, location, true);
334 }
335 value
336}
337
338/// Track a move with explicit IDs and location (advanced API)
339#[inline(always)]
340pub fn track_move_with_id<T>(
341 #[cfg_attr(not(feature = "track"), allow(unused_variables))] from_id: usize,
342 #[cfg_attr(not(feature = "track"), allow(unused_variables))] to_id: usize,
343 #[cfg_attr(not(feature = "track"), allow(unused_variables))] to_name: &str,
344 #[cfg_attr(not(feature = "track"), allow(unused_variables))] location: &str,
345 value: T,
346) -> T {
347 #[cfg(feature = "track")]
348 {
349 let mut tracker = TRACKER.lock();
350 tracker.record_move_with_id(from_id, to_id, to_name, location);
351 }
352 value
353}
354
355/// Track a drop with explicit ID and location (advanced API)
356#[inline(always)]
357pub fn track_drop_with_id(
358 #[cfg_attr(not(feature = "track"), allow(unused_variables))] id: usize,
359 #[cfg_attr(not(feature = "track"), allow(unused_variables))] location: &str,
360) {
361 #[cfg(feature = "track")]
362 {
363 let mut tracker = TRACKER.lock();
364 tracker.record_drop_with_id(id, location);
365 }
366}
367