# debuggable
**Make your Rust types readable in GDB and LLDB with one derive.**
[](https://github.com/tommantonclery/debuggable/actions/workflows/ci.yml)
[](https://crates.io/crates/debuggable)
[](https://docs.rs/debuggable)
Debuggers show the *representation* of your types: pointers, capacities, bookkeeping fields,
and slots that aren't in use. `debuggable` lets you describe what a value *means*, once, next to
the type, and every user of your crate sees that in GDB, LLDB and VS Code.
```text
(gdb) print buf before
$1 = my_crate::RawBuf {ptr: core::ptr::non_null::NonNull<u16> {pointer: 0x5555555aded0}, len: 3, cap: 4}
(gdb) print buf after
$1 = 3/4 = {len = 3, cap = 4, [0] = 10, [1] = 20, [2] = 30}
```
```text
(lldb) v map before
(my_crate::SlotMap<u8, alloc::string::String>) map = {
slots = size=3 {
[0] = {...}
[1] = {...}
[2] = {...}
}
free_head = 3
len = 2
_k =
}
(lldb) v map after
(my_crate::SlotMap<u8, alloc::string::String>) map = 2 items {
[0] = v1 Some("alpha") {...}
[1] = v3 Some("beta") {...}
}
```
The third slot is free (`len` is 2), so `debuggable` doesn't show it. (`{...}` marks collapsed
values, as in VS Code's variables view.)
## Make your crate debuggable in 5 minutes
**1. Add the dependency**
```sh
cargo add debuggable
```
**2. Describe your types**
```rust
use debuggable::Debuggable;
use std::marker::PhantomData;
use std::ptr::NonNull;
#[derive(Debuggable)]
#[debuggable(summary = "{len} items")]
pub struct SlotMap<K, V> {
#[debuggable(items, len = "len")] // show the live slots as elements
slots: Vec<Slot<V>>,
#[debuggable(hide)] // bookkeeping nobody wants to see
free_head: u32,
#[debuggable(hide)]
len: u32,
_k: PhantomData<K>, // PhantomData is hidden automatically
}
#[derive(Debuggable)]
#[debuggable(summary = "{len}/{cap}")]
pub struct RawBuf {
#[debuggable(items, len = "len")] // a raw pointer + length works too
ptr: NonNull<u16>,
len: usize,
cap: usize,
}
#[derive(Debuggable)]
pub enum Token {
#[debuggable(summary = "Ident({name})")]
Ident { name: String },
#[debuggable(summary = "Num({0})")]
Num(i64),
Eof, // no summary: shown as `Eof`
}
#[derive(Debuggable)]
#[debuggable(summary = "v{version} {value}")]
pub struct Slot<V> {
value: Option<V>,
#[debuggable(hide)]
version: u32,
}
```
Mistakes are compile errors that point at the problem:
```text
error: summary refers to unknown field `lenght`; did you mean `length`?
--> src/lib.rs:2:24
|
```
**3. Set up your debugger (once per machine; GDB once per project)**
```sh
cargo install cargo-debuggable
cargo debuggable setup --dry-run # see what it would change
cargo debuggable setup
```
This configures whichever of GDB, LLDB and VS Code (CodeLLDB) you have. It only edits clearly
marked blocks in your config files, backs each file up first, and `cargo debuggable setup --remove`
undoes everything exactly.
**4. Debug as usual**: `rust-gdb`, `rust-lldb`, `lldb`, or F5 in VS Code with CodeLLDB.
**5. If something doesn't show up**
```sh
cargo debuggable doctor target/debug/my-app
```
It checks your debuggers, your configuration and the binary, and tells you the exact fix. See also
[troubleshooting](docs/troubleshooting.md).
## For library authors
Your users get the visualizers automatically: the descriptions are compiled into their binaries
along with your types. They only run `cargo debuggable setup` once, which they may have done
already for another crate.
What it costs them:
- **Binary size:** about 300 bytes per derived type, plus a 5 KB runtime once per Linux binary.
This is data in a section the debugger reads; no code runs in your program. To leave it out,
build with `RUSTFLAGS="--cfg debuggable_disable"`.
- **Compile time:** about 1 ms per derived type on rebuilds without incremental compilation (0.5–1.0 ms measured; guarded in CI). The derive uses `syn` 3, which
`serde`, `tokio`, `thiserror` and `clap` already pull in, so most projects compile nothing extra.
- **Code:** none. No `unsafe` is required in your crate, and it works under
`#![forbid(unsafe_code)]`.
- **MSRV:** Rust 1.75.
## Attributes
| `summary = "..."` | struct, enum variant | One-line summary. `{field}` (or `{0}` for tuple fields) inserts a field; `{{` and `}}` are literal braces. |
| `hide` | field | Hide the field. `PhantomData` fields are hidden automatically. |
| `rename = "..."` | field | Show the field under another name. |
| `items` | field | Show the field's elements in place of the field: a `Vec<T>`, an array `[T; N]`, or a `*const T`, `*mut T` or `NonNull<T>` together with `len`. `MaybeUninit<T>` elements are shown as `T`. |
| `text` | field | Show the field's bytes as a string, `"hello"`: a `[u8; N]`, `Vec<u8>` or byte pointer, with `len` as for `items`. Invalid UTF-8 shows as `\xNN`. |
| `only = "..."` | with `items` | Keep only some elements: a variant name (`"Occupied"`), or a field that is non-zero, optionally masked (`"version & 1"`). For slab-, arena- and slot-map-style collections. |
| `value = "path"` | with `items` | Show this field of each element instead of the whole element (`"0"`, `"u.value"`). |
| `len = "field"` | with `items` or `text` | The number of elements to show: for a `Vec` or array, at most this many; for a pointer, required. |
Full reference: [docs.rs/debuggable](https://docs.rs/debuggable).
## Support
| Linux (x86-64) | 15 | 18, 20, 22 | yes |
| macOS | n/a | Apple LLDB | expected (not yet tested) |
| Windows (MSVC) | not yet: nothing is emitted, so your crate builds unchanged | | |
Every GDB and LLDB version above is tested in CI on every commit, in debug, release, thin-LTO and
fat-LTO builds; VS Code was tested by hand on Linux. Details and known issues: [compatibility](docs/compatibility.md).
## Limitations
- Types defined inside function bodies aren't matched by the debugger (`cargo debuggable doctor` points them out).
- Summaries refer to fields by name; there is no expression language or format specs (yet).
- Windows/Natvis isn't supported yet.
- In plain `gdb`, standard library types (`String`, `Vec`, ...) inside your summaries show raw; use
`rust-gdb`, which loads rustc's printers for them.
## How it works
The derive embeds a small JSON description of each type in the binary. On Linux, a section
GDB auto-loads also holds a 5 KB runtime that renders those descriptions, so GDB needs no setup
beyond trusting your build directory. LLDB can't auto-load, so `cargo debuggable setup` installs
a loader that reads the same descriptions. Details: [how it works](docs/how-it-works.md).
## License
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT license](LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in
this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without
any additional terms or conditions.