tabled: Rust table rendering as a settings pipeline instead of a format string
An easy to use library for pretty print tables of Rust structs and enums.
At a glance
- What is it?
- A workspace of ten crates where tables are built from a builder or a derive macro, then styled by stacking composable settings onto each other.
- Who is it for?
- tabled earns its place when the table is the program, not a debug print. The settings model composes, the style list covers the twelve looks most Rust CLIs actually need, and the derive macro means the column list follows your struct rather than a hand-maintained header row.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 36 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two ways in, and the choice is about whether you know the shape
There are two entry points and the README explains when to pick each. A builder pattern is for when the data schema is unknown at compile time. A typed `Tabled` implementation is for when you have a struct and want the columns to follow it.
The typed path is a derive:
use tabled::{Tabled, Table};
use tabled::assert::assert_table;
#[derive(Tabled)]
struct Language {
name: &'static str,
designed_by: &'static str,
invented_year: usize,
}
let languages = vec![
Language { name: "C", designed_by: "Dennis Ritchie", invented_year: 1972 },
Language { name: "Go", designed_by: "Rob Pike", invented_year: 2009 },
Language { name: "Rust", designed_by: "Graydon Hoare", invented_year: 2010 },
];
let table = Table::new(languages);`Table::new` takes the vector directly and derives headers from field names. That is the whole happy path, and it is why the library is easy to adopt: add a derive, wrap a vector, print.
The builder path assembles a grid cell by cell, which is what you need when rows and columns do not correspond to any struct:
use tabled::{builder::Builder, settings::Style};
use tabled::assert::assert_table;
let mut b = Builder::with_capacity(3, 0);
b.push_record(["row=0 col=0", "row=0 col=1"]);
b.push_column(["row=0 col=2", "row=1 col=2"]);
let mut table = b.build();
table.with(Style::modern());Note that styling is a separate call after building. That separation is the design: layout first, appearance second.
A settings model that stacks rather than a style enum
The README's table of contents is longer than its prose, and that is the honest shape of this library. Style is only the first axis.
Twelve named styles ship in the box: `ascii`, `modern`, `sharp`, `rounded`, `extended`, `psql`, `markdown`, `re_structured_text`, `dots`, `ascii_rounded`, `blank` and `empty`. The `psql` and `markdown` entries matter for a Rust CLI specifically, because matching Postgres output and matching a Markdown table are the two cases where a pretty table also has to be something a person or a renderer will parse.
Past Style, the settings list covers cell borders, text on borders, border colorization, a theme system with layout and content colorization, column name handling, border span correction, alignment, format, padding, margin, shadow, width, height, rotation, removal, extraction, header/footer/panel, merge, concat, highlight, span and split. That is a long list, and it is best read as evidence that this is a composable pipeline rather than a formatter.
Several entries are worth singling out because they solve problems that come up constantly. `Width` has sub-settings for truncate, wrapping, increase width, justify, priority and percent, which is how you get wrapping that respects a terminal width. `Remove` and `Extract` exist so a table can be trimmed or have a column pulled out of it. `Split` suggests you can slice a large table into pieces.
The prose for this section introduces the list of available settings and then stops mid-sentence, with the settings themselves documented on docs.rs rather than on the README page.
Ten crates in the workspace and what each one is for
The root `Cargo.toml` declares a workspace with ten members, and the split is more informative than it first looks.
Two of them are the core. `papergrid` is the grid engine that does the actual cell placement, border drawing and span arithmetic; `tabled` is the public facade that re-exports the settings, the derive helpers and the table types. If you read anything in this repository, `papergrid` is where the interesting algorithms are.
One is the derive: `tabled_derive` implements the `Tabled` attribute macro. Another is compile-time table construction: `static_table`, which backs the `static_table!` macro mentioned in the README's tips section.
Four are format bridges. `json_to_table`, `csv_to_table`, `ron_to_table` and `toml_to_table` each deserialize into a table, and `table_to_html` goes the other way by rendering HTML. The README's formats section documents all five, which makes tabled a reasonable middle layer in a pipeline: read CSV, shape it, write Markdown or HTML.
The tenth is `testing_table`, the crate behind `assert_table!`. That macro is what makes the README's examples self-checking, and it is used throughout the project's own test suite, so it is a real dependency of the examples rather than decoration.
Five table types for different cost profiles
The README's table-types section lists five types and the choice between them is about memory and speed, not about looks.
`Table` is the default: it owns a full object model of the grid. It is the most capable and the most convenient, and it is what `Table::new` returns. `ExtendedTable` extends the default with more manipulation methods, for the cases where you intend to restructure the table after building it.
Then there are three that exist for throughput. `IterTable` renders straight from an iterator without materializing the grid. `CompactTable` is described in the README as being for fast rendering with no footprint, which is the strongest performance claim in the project and the one with the least explanation attached. `Table::kv` produces key-value output, which is the two-column case and comes up whenever you are dumping configuration rather than data.
The practical advice is that if you are rendering a table inside a loop, or in a hot path, the default `Table` allocates and frees a grid structure per call and that is the part worth replacing. `IterTable` and `CompactTable` are the two answers, and picking between them comes down to how much post-build manipulation you need.
The repository is MIT licensed and its topics include `prettytable`, which tells you what it is positioned against. It has no GitHub releases, so version tracking goes through crates.io and docs.rs rather than release notes.
Derive options for when field names are not column names
A derive macro that mirrors struct fields will produce wrong column headers sooner or later. The README's derive section covers the escape hatches, and they are the part worth reading before you commit to the macro.
There is an attribute to override a column name, one to hide a column entirely, and one to set column order explicitly, which is what you want when the natural field order is not the presentation order. There are attributes to format fields and to format headers, so a value can be rendered differently from its raw `Display` output, and an `Inline` option.
The tips and tricks section goes further, into territory that is less about tables than about working around the borrow checker while building them. It covers the `std::fmt::*` format options, tuple combination and tuple options for packing several values into one cell, an object helper, the builder, and the `col!` and `row!` macros. That list is a fair signal about what this library is for: dense, formatted, multi-row output where the presentation carries real information.
One detail in the repository is worth flagging for anyone reading along. The README documents a `PoolTable` type in its table-types section, and the README's own link for examples points at an `examples` folder. Neither a `pool_table` crate nor an `examples` directory appears in the workspace manifest or the tree. That does not mean the type is absent, since it could live inside the `tabled` crate, but it does mean the manifest and the README describe the workspace differently. The docs.rs page for the `tabled` crate is the place to settle it.
Notes on charset, ANSI codes and emoji
A library that draws boxes has to make hard decisions about characters, and the README collects them under Notes where most projects would leave them implicit.
The charset section concerns what happens when data contains characters wider than one column, which is the whole reason the `autoSize` feature in a different project has to count CJK code points as two. For tabled, the question is what a width calculation assumes, and the answer depends on which table type you picked, since the streaming types have less opportunity to measure anything in advance.
The ANSI escape codes section addresses a related trap: a string containing escape sequences has a different display width than its byte or character count suggests. If you render colored output from `colored` or a similar crate through tabled, column alignment drifts unless the table accounts for it.
The emoji section is the same problem taken further, because emoji are frequently two code points wide and are rendered inconsistently across terminals. This is not a solved problem in any language, and a table library cannot fix it, only choose a convention.
The remaining notes cover semver and MSRV policy. For a library people put in production CLIs, the MSRV policy is the one that affects whether you can upgrade: it tells you whether bumping the version can force a toolchain upgrade on you. The project is not archived and its last push was 2026-09-05.
Editorial conclusion
tabled earns its place when the table is the program, not a debug print. The settings model composes, the style list covers the twelve looks most Rust CLIs actually need, and the derive macro means the column list follows your struct rather than a hand-maintained header row. The costs are honest ones: a dependency tree this size is real, the derive macro couples your types to a presentation concern, and CompactTable and IterTable exist because the default `Table` builds a full object model that is the wrong shape for a hot loop. If you only need to eyeball a struct while debugging, the standard library's `Debug` output is faster to reach for. Otherwise start at the usage example in the README, then read the settings list on docs.rs, which covers considerably more than the page itself does.
Frequently asked questions
How do I print a Rust struct as a table?
Derive `Tabled` on the struct and pass a vector of values to `Table::new`, which takes the column headers from the field names. When the field names are not the headers you want, the derive supports attributes for overriding a column name, hiding one, reordering columns and formatting values.
Does tabled support Markdown and Postgres table styles?
Yes. `markdown` and `psql` are both built-in styles alongside ten others including `ascii`, `modern`, `rounded`, `sharp`, `extended`, `dots`, `blank` and `empty`. There are also separate crates that read `json`, `csv`, `ron` and `toml` into a table and one that renders a table to HTML.
Which tabled table type should I use in a performance-sensitive loop?
Avoid the default `Table` there, because it builds a full grid object model per call. `IterTable` renders directly from an iterator without materializing that structure, and `CompactTable` is described in the README as being for fast rendering with no footprint. `ExtendedTable` is the option when you need the extra manipulation methods after building.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zhiburt-tabled)