#[debugger_visualizer(...)] / #[collapse_debuginfo]Attribute
Explanation
Both attributes exist purely to improve the experience of debugging Rust code in an external debugger — neither changes what the program computes or how it runs; they only change what a debugger UI shows while stepping through or inspecting it.
#[debugger_visualizer(natvis_file = "...")] (or the GDB
pretty-printer-script equivalent form) attaches a custom visualization
script to a type, embedded into the compiled binary's debug information
so a debugger that understands the format can find and use it
automatically. Without one, a debugger's default view of a value is
usually just its raw fields laid out as the type's memory layout happens
to place them — readable, but not necessarily meaningful. A Natvis file
(Microsoft's XML-based format, understood by Visual Studio and
Visual Studio Code's C++/Rust debugging extensions) or a GDB
pretty-printer script instead tells the debugger how to render a value in
a form that actually reflects what it represents — a custom RingBuffer
type showing its logical contents in order, say, rather than a raw head
index and backing array a reader would have to interpret by hand.
#[collapse_debuginfo], placed on a macro_rules! macro definition,
controls whether code generated by expanding that macro is attributed, in
debug information, to the macro's own definition site or to each
individual call site where the macro was invoked. This affects how a
debugger single-steps through macro-generated code: attributed to the
call site, stepping through an invocation feels like stepping through
ordinary inline code at the point of the call; attributed to the
definition site (the default for some macros), a debugger can instead jump
into the macro's own source location, which is sometimes what's wanted (to
inspect the macro's expansion itself) and sometimes just confusing
noise for a macro whose internals nobody stepping through calling code
cares about.
Both are niche, debugging-tooling-focused attributes. Most Rust code never needs either one — they matter specifically to authors of libraries whose types or macros are widely used and would benefit from a noticeably better debugging experience for everyone downstream, not to day-to-day application code.
Usage examples
Attributing a macro's expanded code to its call site
#[collapse_debuginfo(yes)] // <- attributes this macro's expanded code to each call site, not its own definition
macro_rules! double {
($value:expr) => {
$value * 2
};
}
Designing a public API
A widely-used logging macro expands to a fair amount of internal
plumbing; without #[collapse_debuginfo], stepping through a call to it
in a debugger can drop the user into the macro's own definition rather
than staying at the call site the developer actually cares about.
#[collapse_debuginfo(yes)] // <- keeps single-stepping at the call site, not inside the macro's own expansion
macro_rules! log_event {
($msg:expr) => {
println!("[event] {}", $msg)
};
}
fn process_order(order_id: u32) {
log_event!(format!("processing order {order_id}")); // <- debugger steps through this line, not the macro body
}
A caller stepping through process_order is almost
always interested in their own control flow, not in re-deriving how
log_event! itself expands — the
Rust Reference
documents collapse_debuginfo as the mechanism for choosing that behavior
per macro, appropriate for macros meant to feel like ordinary statements
rather than something a caller needs to debug into.