Crate borrowscope_macro

Crate borrowscope_macro 

Source
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

AttributeDescription
#[trace_borrow]Standard tracking (recommended)
#[trace_borrow(quiet)]Ownership only (new, move, drop, borrow)
#[trace_borrow(verbose)]All tracking including noisy features

§Feature Selection

AttributeDescription
#[trace_borrow(skip = "loops,branches")]Skip specific feature groups
#[trace_borrow(only = "ownership")]Enable only specified feature groups

§Filtering & Sampling (Performance)

AttributeDescription
#[trace_borrow(filter = "data*")]Only track variables matching glob pattern
#[trace_borrow(sample = 0.1)]Track ~10% of operations (probabilistic)

§Conditional Compilation

AttributeDescription
#[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:

GroupAliasesDescription
ownership-Variable creation, moves, drops, borrows
smart_pointerspointersRc, Arc, RefCell, Cell operations
loops-for, while, loop tracking
branches-if/else, match tracking
control_flowcontrolbreak, continue, return
try-? operator
methods-clone, lock, unwrap
async-async blocks, await
unsafe-unsafe blocks, raw pointers, transmute
expressionsexprsstruct, tuple, array, range, cast
functionsfnFunction 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 PatternEvent
let x = value;New
let y = &x;Borrow
let y = &mut x;Borrow (mutable)
let y = x; (move)Move
Scope exitDrop

§Smart Pointers (smart_pointers group)

Code PatternEvent
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 PatternEvent
for/while/loop entryLoopEnter
Each iterationLoopIteration
Loop endLoopExit

§Branches (branches group)

Code PatternEvent
if/elseBranch
match entryMatchEnter
Match arm takenMatchArm
Match endMatchExit

§Control Flow (control_flow group)

Code PatternEvent
breakBreak
continueContinue
returnReturn

§Other Groups

GroupCode PatternsEvents
tryexpr?Try
methods.clone(), .lock(), .unwrap()Clone, Lock, Unwrap
asyncasync { }, .awaitAsyncBlockEnter/Exit, AwaitStart/End
unsafeunsafe { }, *ptr, transmuteUnsafeBlockEnter/Exit, RawPtrDeref, Transmute
expressionsstructs, tuples, arrays, ranges, castsStructCreate, TupleCreate, etc.
functionsfn entry/exitFnEnter, FnExit

§Advanced Smart Pointer Tracking

Beyond basic Rc, Arc, RefCell, and Cell, the macro tracks:

§Weak References

Code PatternEvent
Rc::downgrade(&rc)WeakNew
Arc::downgrade(&arc)WeakNewSync
weak.upgrade()WeakUpgrade / WeakUpgradeSync
weak.clone()WeakClone / WeakCloneSync

§Pin, Cow, OnceCell, MaybeUninit

TypeOperations
BoxBox::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 PatternEvent
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 PatternEvent
Point { x, y }StructCreate (with type name)
(a, b, c)TupleCreate (with arity)
[1, 2, 3]ArrayCreate (with length)
0..10Range (half_open)
0..=10Range (closed)
x as i64TypeCast (with target type)

§Closure Tracking

Code PatternEvent
|x| x + 1ClosureCreate (capture mode: ref)
move |x| x + 1ClosureCreate (capture mode: move)
Captured variableClosureCapture (per variable)

§Diagnostic Options

For patterns that cannot be auto-detected, use diagnostic attributes:

AttributeDescription
#[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:

  1. Parsing the function into an AST using syn
  2. Walking the AST with OwnershipVisitor that 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.)
  3. Injecting borrowscope_runtime::track_* calls
  4. 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

  1. Use quiet mode for minimal overhead when you only need ownership tracking
  2. Use filter to track only relevant variables (zero overhead for non-matching)
  3. Use sample for high-frequency code paths
  4. Use debug_only to eliminate all overhead in release builds
  5. Use skip to 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_runtime has features = ["track"] enabled
  • Call reset() before the traced function
  • Check if debug_only is set but running in release mode

Too many events:

  • Use quiet mode or only = "ownership"
  • Use skip = "loops,branches" to reduce noise
  • Use filter to track specific variables

Performance issues:

  • Use sample = 0.1 or lower for high-frequency code
  • Use debug_only to disable in release builds
  • Use filter to reduce tracked variables

Attribute Macros§

trace_borrow
Attribute macro to trace ownership and borrowing in a function.