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
| Attribute | Effect |
|---|---|
right | Right-align the column |
center | Center-align the column |
width_fixed = N | Set a fixed character width |
width_fraction = F | Set width as a fraction of terminal width |
truncate | Truncate data exceeding the column width |
wrap | Wrap long data across multiple output lines |
strict_width | Never shrink below the width hint |
no_extremes | Ignore outlier values in width calculation |
Display control
| Attribute | Effect |
|---|---|
header = "NAME" | Override the column header (default: field name uppercased) |
hidden | Store in memory but don’t display |
skip | Don’t create a column at all (field is ignored entirely) |
Tree rendering
| Attribute | Effect |
|---|---|
tree | Mark as the tree hierarchy column |
children | Use this Vec<Self> field as the tree children source |
JSON control
| Attribute | Effect |
|---|---|
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-isu8..u128,i8..i128,f32,f64–.to_string()bool–"true"/"false"Option<T>– inner value’s string, or""ifNone
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 childrento_table_with(items, headers)– build a table showing only the selected columnsprint_table(items, writer)– build and print in one steptable()– 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 (lastcontrols└─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();