Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Derive Macro

The derive feature provides a #[derive(Cols)] macro that generates table column definitions, row conversion, and a typed header enum from a struct. This replaces the manual column setup for the common case of displaying a collection of structs.

Setup

Enable the derive feature in your Cargo.toml:

[dependencies]
cols = { version = "0.2", features = ["derive"] }

Basic usage

Annotate a struct with #[derive(Cols)]. Each field becomes a column, with the header defaulting to the field name in uppercase.

use cols::{Cols, print_table};

#[derive(Cols)]
struct Server {
    name: String,
    ip: String,
    port: u16,
    status: String,
}

let servers = vec![
    Server { name: "web-1".into(), ip: "10.0.0.1".into(), port: 443, status: "up".into() },
    Server { name: "web-2".into(), ip: "10.0.0.2".into(), port: 443, status: "down".into() },
];

let table = Server::to_table(&servers);
print_table(&table, &mut std::io::stdout().lock()).unwrap();

Output:

NAME   IP        PORT STATUS
web-1  10.0.0.1   443 up
web-2  10.0.0.2   443 down

Field attributes

Use #[column(...)] on fields to control column behavior. All attributes are optional.

Layout and alignment

AttributeEffect
rightRight-align the column
centerCenter-align the column
width_fixed = NSet a fixed character width
width_fraction = FSet width as a fraction of terminal width
truncateTruncate data exceeding the column width
wrapWrap long data across multiple output lines
strict_widthNever shrink below the width hint
no_extremesIgnore outlier values in width calculation

Display control

AttributeEffect
header = "NAME"Override the column header (default: field name uppercased)
hiddenStore in memory but don’t display
skipDon’t create a column at all (field is ignored entirely)

Tree rendering

AttributeEffect
treeMark as the tree hierarchy column
childrenUse this Vec<Self> field as the tree children source

JSON control

AttributeEffect
json_type = "Number"Set the JSON serialization type (String, Number, Boolean)
json_key = "key"Override the JSON object key

Type conversion

The to_row() method converts each field value to a string automatically:

  • String / &str – used as-is
  • u8..u128, i8..i128, f32, f64.to_string()
  • bool"true" / "false"
  • Option<T> – inner value’s string, or "" if None

Header enum

The derive macro generates a header enum named {Struct}Header with one variant per visible column (excluding skip fields). This enum lets you select columns programmatically.

#[derive(Cols)]
struct Device {
    name: String,
    size: u64,
    #[column(skip)]
    internal: String,
}

// Generated: DeviceHeader with variants Name and Size (not Internal)

// Show only selected columns
let table = Device::to_table_with(
    &devices,
    &[DeviceHeader::Name, DeviceHeader::Size],
);

// Look up a column index
let idx = DeviceHeader::Size.index();  // 1
let name = DeviceHeader::Size.name();  // "SIZE"

The header enum implements Debug, Clone, Copy, PartialEq, and Eq. It also implements the ColsHeader trait, which provides all(), index(), and name().

Tree rendering

Mark one field with #[column(tree)] and another with #[column(children)] to get automatic tree rendering. The children field must be a Vec<Self>.

#[derive(Cols)]
struct FsEntry {
    #[column(tree)]
    name: String,
    size: u64,
    #[column(children)]
    entries: Vec<FsEntry>,
}

let root = FsEntry {
    name: "/".into(),
    size: 4096,
    entries: vec![
        FsEntry { name: "bin".into(), size: 2048, entries: vec![] },
        FsEntry { name: "etc".into(), size: 1024, entries: vec![] },
    ],
};

let table = FsEntry::to_table(&[root]);

Output:

NAME  SIZE
/     4096
├─bin 2048
└─etc 1024

Both to_table and stream_to recurse into children automatically.

Convenience methods

The Cols trait provides several methods beyond columns() and to_row():

  • to_table(items) – build a complete table from a slice, recursing into children
  • to_table_with(items, headers) – build a table showing only the selected columns
  • print_table(items, writer) – build and print in one step
  • table() – create an empty table with columns configured (useful for streaming)
  • stream_to(writer, parent, last) – stream this item and its children to a streaming writer (last controls └─ vs ├─ tree connectors)

Streaming

Combine the derive macro with the streaming writer for incremental output:

use cols::{Cols, TermForce};

let mut table = Device::table();
table.termforce_set(TermForce::Always);
table.termwidth_set(80);

let mut out = std::io::stdout().lock();
let mut writer = table.streaming_writer(&mut out).unwrap();

let count = devices.len();
for (i, device) in devices.iter().enumerate() {
    device.stream_to(&mut writer, None, i == count - 1).unwrap();
}
writer.close().unwrap();

Output modes

Tables built with the derive macro support all output modes. Set the mode on the table after building it:

let mut table = Device::to_table(&devices);
table.output_mode_set(OutputMode::Json);
table.name_set("devices");
print_table(&table, &mut std::io::stdout().lock()).unwrap();