How to pad and align text in a table
Short answer: The format spec does it. {:<10} pads a cell on the right, {:>10} on the left,
{:^10} on both, and the character before the alignment flag is the fill. Strings left-align by
default and numbers right-align. Width is a minimum, so a longer cell overflows its column; {:.4}
cuts it, and {:<6.6} does both. When the width comes from the data, {:width$} reads it from a
variable. A crate draws the borders.
From a fresh project — cargo new pad-demo — replace src/main.rs with each block below and run it
with cargo run. The project name only shows up in cargo’s own output; nothing else on this page
depends on it.
The defaults, said out loud
Section titled “The defaults, said out loud”fn main() { let rows = [("Ada", 1843), ("Grace", 1952), ("Edsger", 1972)]; for (name, year) in rows { println!("{name:<10}{year:>6}"); }}Ada 1843Grace 1952Edsger 1972A column lines up when every cell is padded to the same width. {:<10} pads on the right to at least
ten characters and {:>6} on the left to six, so the name column and the year column now share an
edge. The defaults would have produced the same table here — strings left-align and numbers
right-align — but the default follows the type, not the meaning. A column of years held as text
left-aligns with the names. Write the alignment you mean.
The fill character
Section titled “The fill character”fn main() { println!("{:*^24}", " Rust Table "); println!("{:<12}{:>12}", "name", "score"); println!("{:-<12}{:->12}", "", "");}****** Rust Table ******name score------------------------The character before the alignment flag is the fill. Spaces are the default; here * centres the
title and - draws the rule under the header. 0 is the fill people meet first, from {year:05},
and it is sign-aware: format!("{:06}", -42) gives -00042, with the sign in front of the zeros.
When the width comes from the data
Section titled “When the width comes from the data”Hard-coding six works until a longer name arrives. Measure the widest cell, then {:<width$} takes
the width from a variable:
fn main() { let names = ["Ada", "Grace", "Edsger"]; let width = names.iter().map(|n| n.chars().count()).max().unwrap(); println!("widest: {width}"); for name in names { println!("{name:<width$}|"); } println!("chars: {} bytes: {}", "é".chars().count(), "é".len());}widest: 6Ada |Grace |Edsger|chars: 1 bytes: 2The measure is chars().count(), not len(). Padding counts characters, so an accented name is one
unit wide here even though it is two bytes on the wire. The last two lines show it.
Width is a minimum, not a maximum
Section titled “Width is a minimum, not a maximum”fn main() { for cell in ["Ada", "Lovelace"] { println!("[{:>6}]", cell); }}[ Ada][Lovelace]The width is a minimum, not a cut. A cell wider than its column pushes everything after it out of
line: Lovelace is eight characters in a six-wide column. Precision is the cut: {:.4} keeps four
characters. The two flags combine:
fn main() { for cell in ["Ada", "Lovelace"] { println!("[{:>6.6}]", cell); } println!("[{:.2}]", "héllo");}[ Ada][Lovela][hé]{:<6.6} truncates to six characters and then pads to six, which is the usual spelling for a capped
column. The cut counts characters, not bytes, so {:.2} on "héllo" is hé — the accented character
survives whole. Slicing by hand at byte two would split it, which is the panic
the slicing page is about.
The crate that does it for you
Section titled “The crate that does it for you”std pads cells; it does not draw borders or measure the columns for you. That part is a crate’s job,
and a small one. tabled pretty-prints tables of Rust structs and enums, and comfy-table builds
tables with automatic content wrapping. Hand either one the rows and it measures every column and
draws the grid. Given Ada and Grace Hopper, both widen the first column to fit the longer name
without being told.
Related
Section titled “Related”- How to format strings with println! and format! — the whole format mini-language, and where the flags come from
- How to slice a string safely — char boundaries, and the panic a byte-precision cut avoids
- How to trim whitespace and strip characters — cleaning a cell before it gets padded
- Working with text — the rest of the chapter