---
title: "5.5 Formatting strings"
description: "format! and the formatting mini-language: width, alignment, precision, zero-padding, and the {:?} debug view."
url: "https://learnrust.net/chapter-5/formatting-strings/"
last_updated: "2026-06-12"
---

# 5.5 Formatting strings

This course has been writing IOUs against this lesson since chapter 2, and it's time to pay all of them at once. Lesson [2.4](https://learnrust.net/chapter-2/local-scope-and-blocks/) name-dropped `format!` as "println!'s sibling that produces the text instead of printing it." Lesson [4.5](https://learnrust.net/chapter-4/floating-point-types/) pulled `{:.17}` out of a hat to expose float precision. Lessons [4.4](https://learnrust.net/chapter-4/integer-overflow/) and [4.11](https://learnrust.net/chapter-4/tuples-and-the-unit-type/) both promised that `{:?}` would be explained here. And the [chapter 4 quiz](https://learnrust.net/chapter-4/chapter-4-summary-and-quiz/) left a ball printing its height as `21.599999999999994 meters` at a tower whose grammar said "At 1 seconds." Every one of those debts comes due below.

## format!

`format!` works exactly like `println!`: same placeholders, same argument rules, same inline `{name}` captures you've used since lesson 1.6. The only difference is the destination. `println!` sends the finished text to the terminal; `format!` hands it back to you as a brand-new `String`:

```rust
fn main() {
    let name = "Ada";
    let score = 97;

    let line = format!("{name} scored {score} points");
    println!("{line}");
    println!("That line is {} bytes long.", line.len());
}
```

```
Ada scored 97 points
That line is 20 bytes long.
```

That's the whole macro. Its value is what it unlocks: text you can store, measure, grow with `push_str`, or pass to a function, instead of text that's already gone to the screen. It's also the promised better concatenator from lesson [5.3](https://learnrust.net/chapter-5/introduction-to-string/): `format!("{first} {last}")` reads better than a `push_str` chain and consumes nothing.

(One bit of housekeeping while we're here: to print a literal brace, double it. `println!("{{}}")` prints `{}`.)

## The mini-language inside the braces

Everything else in this lesson applies to `println!` and `format!` equally, because the braces speak a shared formatting mini-language. The part after the `:` is a spec, and you already know its shape from `{:.17}`. Here's the tour, one knob at a time.

**Width** pads a value to a minimum number of characters: `{:6}` means "at least 6 wide." Numbers pad on the left, text pads on the right, and you can override either with an **alignment**: `<` left, `>` right, `^` center:

```rust
fn main() {
    println!("[{:>6}]", 42);
    println!("[{:<6}]", 42);
    println!("[{:^6}]", 42);
    println!("[{:>6}]", "hi");
}
```

```
[    42]
[42    ]
[  42  ]
[    hi]
```

The brackets are just to make the padding visible; width is invisible by design.

**Precision**, for floats, is the `.N` you met in 4.5: `{:.2}` rounds to two decimal places (and pads with zeros if needed). It's the civilized-printing tool the falling ball was promised:

```rust
fn main() {
    let height: f64 = 100.0 - 9.8 * 4.0 * 4.0 / 2.0;
    println!("At 4 seconds, the ball is at height: {height} meters");
    println!("At 4 seconds, the ball is at height: {height:.1} meters");
}
```

```
At 4 seconds, the ball is at height: 21.599999999999994 meters
At 4 seconds, the ball is at height: 21.6 meters
```

Note `{height:.1}`: specs ride along happily inside inline captures. And note what precision is *not*: the float still holds the long ugly value, exactly as lesson [4.5](https://learnrust.net/chapter-4/floating-point-types/) taught. `{:.1}` rounds the *displayed text*, not the number. Cosmetics, not surgery.

The knobs combine, in fixed order (fill and alignment, then sign, then width, then precision). A few useful combinations:

```rust
fn main() {
    println!("[{:>8.2}]", 3.14159);
    println!("[{:08.2}]", 3.14159);
    println!("[{:+}]", 42);
    println!("[{:->8}]", "hi");
}
```

```
[    3.14]
[00003.14]
[+42]
[------hi]
```

`{:>8.2}` right-aligns to 8 wide after rounding to 2 places. `{:08}` pads with zeros instead of spaces (handy for things like `007`). `{:+}` prints the sign even when positive. And `{:->8}` shows the general fill form: any character before the alignment arrow becomes the padding, dashes included.

Width plus alignment is how you print tables that line up, which is the kind of output real programs are full of:

```rust
fn main() {
    println!("{:<10} {:>6}", "item", "price");
    println!("{:<10} {:>6.2}", "tea", 2.5);
    println!("{:<10} {:>6.2}", "biscuits", 12.0);
    println!("{:<10} {:>6.2}", "jam", 3.75);
}
```

```
item        price
tea          2.50
biscuits    12.00
jam          3.75
```

Ten wide for names on the left, six wide for prices on the right, decimals at attention like soldiers.

## {:?}, the programmer's view

The last debt is `{:?}`. The plain `{}` placeholder asks a value to display itself *for end users*, and some types decline the job. Tuples, famously, since lesson [4.11](https://learnrust.net/chapter-4/tuples-and-the-unit-type/): there's no one obviously right way to show `(3, 7)` to a human, so `{}` refuses. The alternative spelling `{:?}` asks for the **debug** view instead: a representation *for programmers*, where the only goal is showing the value's structure, warts and all:

```rust
fn main() {
    let point = (3, 7);
    let name = "Ada";
    println!("{:?}", point);
    println!("{:?}", name);
}
```

```
(3, 7)
"Ada"
```

The tuple finally prints. And look closely at the second line: in debug view, text shows its quotes. That's the spirit of `{:?}` in one detail; a user never cares where text begins and ends, but a programmer chasing a stray-whitespace bug absolutely does (`{:?}` on `"42\n"` shows the `\n` that `{}` would helpfully render as a line break, which makes it the perfect tool for inspecting exactly the input problems lesson 5.6 is about). This is also why `dbg!` from lesson [3.4](https://learnrust.net/chapter-3/print-debugging/) always shows the debug view: it's a tool for you, not your users.

Why do some types support `{}` and others only `{:?}`? A type opts into each view by implementing the matching capability, and the system behind that word is *traits*, chapter 16's headline. For now the working rule: `{}` for showing values to people, `{:?}` when you're debugging, and if `{}` ever refuses with "doesn't implement `std::fmt::Display`", switch to `{:?}` and carry on. (There's also a prettier multi-line form, `{:#?}`. On a small tuple it's underwhelming; when chapter 10's structs arrive with a dozen fields, it earns its keep.)

## Quiz time

**Question #1**

Predict all five lines:

```rust
fn main() {
    println!("[{:>5}]", 7);
    println!("[{:<5}]", "ok");
    println!("[{:^7}]", "mid");
    println!("[{:06.2}]", 9.876);
    println!("[{:?}]", ("hi", 2));
}
```

<details class="solution">
<summary>Show solution</summary>

```
[    7]
[ok   ]
[  mid  ]
[009.88]
[("hi", 2)]
```

Line by line: right-aligned in 5; left-aligned in 5; centered in 7 (two spaces each side); rounded to `9.88` then zero-padded to 6 wide; and the tuple in debug view, its text element wearing debug quotes.

</details>

**Question #2**

Using one `format!`, build the string `Total: $  42.50` (two spaces before the number; the amount right-aligned to 7 wide with 2 decimals) from `let total = 42.5;`, store it in a variable, then print it.

<details class="solution">
<summary>Show solution</summary>

```rust
fn main() {
    let total = 42.5;
    let line = format!("Total: ${total:>7.2}");
    println!("{line}");
}
```

```
Total: $  42.50
```

`>7` reserves seven characters and pushes the value right; `.2` makes `42.5` into `42.50` (five characters), leaving two spaces of padding. `${...}` is fine as-is: only braces need escaping, dollar signs are just text.

</details>

**Question #3**

The chapter 4 ball-drop printed `At 1 seconds, the ball is at height: 95.1 meters`, and the grammar pedants were promised satisfaction. Write a function `report_time(seconds: f64, height: f64)` that prints the same report but says `second` when the time is exactly 1 and `seconds` otherwise, with the height shown to one decimal place. (Lesson [4.7](https://learnrust.net/chapter-4/introduction-to-if-expressions/) provides the decision tool; lesson [4.5](https://learnrust.net/chapter-4/floating-point-types/)'s `==` warning doesn't apply here, since the quiz values arrive as exact `1.0`-style constants, not as arithmetic results.)

<details class="solution">
<summary>Show solution</summary>

```rust
fn report_time(seconds: f64, height: f64) {
    let unit = if seconds == 1.0 { "second" } else { "seconds" };
    println!("At {seconds} {unit}, the ball is at height: {height:.1} meters");
}

fn main() {
    report_time(1.0, 95.1);
    report_time(4.0, 21.599999999999994);
}
```

```
At 1 second, the ball is at height: 95.1 meters
At 4 seconds, the ball is at height: 21.6 meters
```

An `if` expression picks the unit (an arm-typed `&str`, both arms agreeing, per lesson 4.7), and `{height:.1}` retires the digit eruption. The pedants may stand down.

</details>

You can now turn any value into exactly the text you want. The one remaining gap in your text toolkit runs the other way: turning text the *user* typed into values you can compute with. That's the incantation you've been copying since lesson 1.12, and next lesson it finally gets taken apart.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
