Expand description
§BorrowScope Procedural Macros
This crate provides the #[trace_borrow] attribute macro that instruments
Rust code to track ownership and borrowing operations at runtime.
§Quick Start
use borrowscope_macro::trace_borrow;
use borrowscope_runtime::*;
#[trace_borrow]
fn example() {
let x = String::from("hello"); // New event
let y = &x; // Borrow event
let z = x; // Move event
} // Drop events
fn main() {
reset();
example();
println!("{:?}", get_events());
}§Attribute Options
§Presets
| Attribute | Description |
|---|---|
#[trace_borrow] | Standard tracking (recommended) |
#[trace_borrow(quiet)] | Ownership only (new, move, drop, borrow) |
#[trace_borrow(verbose)] | All tracking including noisy features |
§Feature Selection
| Attribute | Description |
|---|---|
#[trace_borrow(skip = "loops,branches")] | Skip specific feature groups |
#[trace_borrow(only = "ownership")] | Enable only specified feature groups |
§Filtering & Sampling (Performance)
| Attribute | Description |
|---|---|
#[trace_borrow(filter = "data*")] | Only track variables matching glob pattern |
#[trace_borrow(sample = 0.1)] | Track ~10% of operations (probabilistic) |
§Conditional Compilation
| Attribute | Description |
|---|---|
#[trace_borrow(debug_only)] | Only track in debug builds |
#[trace_borrow(release_only)] | Only track in release builds |
#[trace_borrow(feature = "tracing")] | Only track when cargo feature enabled |
§Feature Groups
Use these group names with skip or only options:
| Group | Aliases | Description |
|---|---|---|
ownership | - | Variable creation, moves, drops, borrows |
smart_pointers | pointers | Rc, Arc, RefCell, Cell operations |
loops | - | for, while, loop tracking |
branches | - | if/else, match tracking |
control_flow | control | break, continue, return |
try | - | ? operator |
methods | - | clone, lock, unwrap |
async | - | async blocks, await |
unsafe | - | unsafe blocks, raw pointers, transmute |
expressions | exprs | struct, tuple, array, range, cast |
functions | fn | Function entry/exit (disabled by default) |
§Filtering
Filter which variables are tracked using glob patterns:
#[trace_borrow(filter = "data*")] // Track vars starting with "data"
#[trace_borrow(filter = "*_count")] // Track vars ending with "_count"
#[trace_borrow(filter = "user_?")] // Track user_1, user_2, etc.Pattern syntax:
*matches zero or more characters?matches exactly one character
Note: Filtering is applied at compile-time. No tracking code is generated for variables that don’t match the pattern, resulting in zero overhead.
§Sampling
Reduce tracking overhead by only recording a percentage of operations:
#[trace_borrow(sample = 0.1)] // Track ~10% of operations
#[trace_borrow(sample = 0.5)] // Track ~50% of operations
#[trace_borrow(sample = 1.0)] // Track 100% (same as no sampling)Use cases:
- High-frequency loops where full tracking is too expensive
- Production monitoring with minimal overhead
- Statistical analysis where sampling is acceptable
Note: Sampling uses a fast PRNG (xorshift64) for minimal overhead. The decision is made at runtime for each tracking call.
§Conditional Compilation
Control when tracking code is included:
// Only in debug builds (recommended for development)
#[trace_borrow(debug_only)]
fn dev_function() { }
// Only in release builds (for production monitoring)
#[trace_borrow(release_only)]
fn prod_function() { }
// Only when cargo feature is enabled
#[trace_borrow(feature = "tracing")]
fn optional_tracing() { }Generated code:
debug_only→#[cfg(debug_assertions)]release_only→#[cfg(not(debug_assertions))]feature = "x"→#[cfg(feature = "x")]
§Combining Options
Multiple options can be combined:
// Debug-only, quiet mode
#[trace_borrow(debug_only, quiet)]
// Filter + sampling for high-performance tracking
#[trace_borrow(filter = "user*", sample = 0.1)]
// Feature-gated with specific groups
#[trace_borrow(feature = "trace", only = "ownership,smart_pointers")]
// Skip noisy features, debug only
#[trace_borrow(debug_only, skip = "loops,branches,expressions")]§Tracked Operations
§Basic Ownership (ownership group)
| Code Pattern | Event |
|---|---|
let x = value; | New |
let y = &x; | Borrow |
let y = &mut x; | Borrow (mutable) |
let y = x; (move) | Move |
| Scope exit | Drop |
§Smart Pointers (smart_pointers group)
| Code Pattern | Event |
|---|---|
Rc::new(v) | RcNew |
Rc::clone(&rc) | RcClone |
Arc::new(v) | ArcNew |
Arc::clone(&arc) | ArcClone |
Box::new(v) | BoxNew |
Box::pin(v) | PinNew |
RefCell::new(v) | RefCellNew |
refcell.borrow() | RefCellBorrow |
refcell.borrow_mut() | RefCellBorrowMut |
Cell::new(v) | CellNew |
cell.get() | CellGet |
cell.set(v) | CellSet |
§Loops (loops group)
| Code Pattern | Event |
|---|---|
for/while/loop entry | LoopEnter |
| Each iteration | LoopIteration |
| Loop end | LoopExit |
§Branches (branches group)
| Code Pattern | Event |
|---|---|
if/else | Branch |
match entry | MatchEnter |
| Match arm taken | MatchArm |
| Match end | MatchExit |
§Control Flow (control_flow group)
| Code Pattern | Event |
|---|---|
break | Break |
continue | Continue |
return | Return |
§Other Groups
| Group | Code Patterns | Events |
|---|---|---|
try | expr? | Try |
methods | .clone(), .lock(), .unwrap() | Clone, Lock, Unwrap |
async | async { }, .await | AsyncBlockEnter/Exit, AwaitStart/End |
unsafe | unsafe { }, *ptr, transmute | UnsafeBlockEnter/Exit, RawPtrDeref, Transmute |
expressions | structs, tuples, arrays, ranges, casts | StructCreate, TupleCreate, etc. |
functions | fn entry/exit | FnEnter, FnExit |
§Advanced Smart Pointer Tracking
Beyond basic Rc, Arc, RefCell, and Cell, the macro tracks:
§Weak References
| Code Pattern | Event |
|---|---|
Rc::downgrade(&rc) | WeakNew |
Arc::downgrade(&arc) | WeakNewSync |
weak.upgrade() | WeakUpgrade / WeakUpgradeSync |
weak.clone() | WeakClone / WeakCloneSync |
§Pin, Cow, OnceCell, MaybeUninit
| Type | Operations |
|---|---|
Box | Box::pin, Box::into_raw, Box::from_raw |
Pin<T> | Pin::new, Pin::into_inner |
Cow<T> | Cow::Borrowed, Cow::Owned, to_mut() |
OnceCell<T> | new(), set(), get(), get_or_init() |
OnceLock<T> | new(), set(), get(), get_or_init() |
MaybeUninit<T> | uninit(), new(), write(), assume_init() |
§Concurrency Tracking
| Code Pattern | Event |
|---|---|
thread::spawn(...) | ThreadSpawn |
handle.join() | ThreadJoin |
mpsc::channel() | ChannelNew |
tx.send(v) | ChannelSend |
rx.recv() | ChannelRecv |
rx.try_recv() | ChannelTryRecv |
§Expression Tracking (expressions group)
| Code Pattern | Event |
|---|---|
Point { x, y } | StructCreate (with type name) |
(a, b, c) | TupleCreate (with arity) |
[1, 2, 3] | ArrayCreate (with length) |
0..10 | Range (half_open) |
0..=10 | Range (closed) |
x as i64 | TypeCast (with target type) |
§Closure Tracking
| Code Pattern | Event |
|---|---|
|x| x + 1 | ClosureCreate (capture mode: ref) |
move |x| x + 1 | ClosureCreate (capture mode: move) |
| Captured variable | ClosureCapture (per variable) |
§Diagnostic Options
For patterns that cannot be auto-detected, use diagnostic attributes:
| Attribute | Description |
|---|---|
#[trace_borrow(warn)] | Emit warnings for ambiguous patterns |
#[trace_borrow(ffi = ["malloc"])] | Declare known FFI functions |
#[trace_borrow(unions = ["MyUnion"])] | Declare known union types |
#[trace_borrow(statics = ["GLOBAL"])] | Declare known static variables |
§How It Works
The macro transforms functions by:
- Parsing the function into an AST using
syn - Walking the AST with
OwnershipVisitorthat maintains:- Unique IDs for each variable (for event correlation)
- Scope stack for LIFO drop ordering
- Type context (tracks which vars are Weak, Cow, OnceCell, etc.)
- Injecting
borrowscope_runtime::track_*calls - Generating drop calls at scope exits in reverse order
§ID-Based Correlation
Each variable gets a unique ID, enabling correlation:
- Borrows link to their owner’s ID
- Clones link to their source’s ID
- Moves link source and destination IDs
§Performance Tips
- Use
quietmode for minimal overhead when you only need ownership tracking - Use
filterto track only relevant variables (zero overhead for non-matching) - Use
samplefor high-frequency code paths - Use
debug_onlyto eliminate all overhead in release builds - Use
skipto disable noisy features like loops and branches
// Minimal overhead configuration
#[trace_borrow(debug_only, quiet, filter = "important_*")]
fn performance_critical() { }§Common Patterns
// Development: full tracking, debug only
#[trace_borrow(debug_only)]
fn dev_function() { }
// Learning: ownership only, cleaner output
#[trace_borrow(quiet)]
fn learning_example() { }
// Production monitoring: sampled, feature-gated
#[trace_borrow(feature = "monitoring", sample = 0.01)]
fn production_function() { }
// Debugging specific variables
#[trace_borrow(filter = "suspect_*", verbose)]
fn debug_specific() { }§Limitations
- const fn: Cannot be used (tracking requires runtime)
- extern fn: Cannot be used (only Rust ABI supported)
- async fn: Works but may not capture all ownership across await points
- unsafe fn: Works but tracking cannot verify safety invariants
- Macros: Variables created inside macro expansions may not be tracked
§Troubleshooting
No events recorded:
- Ensure
borrowscope_runtimehasfeatures = ["track"]enabled - Call
reset()before the traced function - Check if
debug_onlyis set but running in release mode
Too many events:
- Use
quietmode oronly = "ownership" - Use
skip = "loops,branches"to reduce noise - Use
filterto track specific variables
Performance issues:
- Use
sample = 0.1or lower for high-frequency code - Use
debug_onlyto disable in release builds - Use
filterto reduce tracked variables
Attribute Macros§
- trace_
borrow - Attribute macro to trace ownership and borrowing in a function.