Skip to content
Select theme

How to format a number with thousands separators

Short answer: Rust’s format spec has no thousands-separator flag — there is no {:,} to reach for, and nothing to enable. {:,} is what other languages use, and the Rust compiler names that habit in its error. You get separators by formatting the digits and inserting them yourself: a ten-line helper that also composes with the ordinary width and alignment flags.

From a fresh project — cargo new sep-demo, then replace src/main.rs with each block below.

error: invalid format string: python's numeric grouping `,` is not supported in rust format strings
--> src/main.rs:2:17
|
2 | println!("{:,}", 1234567);
| - ^ expected `}` in format string
| |
| because of this opening brace
|
= note: to print `{`, you can escape it using `{{`
error: aborting due to 1 previous error

The diagnostic is worth reading twice, because it settles the question completely: the comma is not part of the format grammar at all — the string fails to parse, before any type is involved. Python’s format(x, ",") and f-strings like f"{x:,}" are where most people learn the flag, and the Rust compiler recognises the muscle memory by name. There is no alternative spelling either: no grouping mode on Display, no option on the numeric types, and no locale machinery anywhere in the standard library. Formatting a number with separators means building that string yourself.

The shape of the problem is easier to see on integers. Take the digits as a string, cut off the short head group that the first comma needs, then walk the rest three characters at a time:

999
1,234,567
-9,876,543
123,456,789,012,345,678,901,234,567,890

Two details carry the correctness. split_at(digits.len() % 3) splits off the head group — "1234567" has seven digits, so the head is one digit and the rest falls into complete threes; when the length is already a multiple of three the head is empty and the loop starts fresh. And unsigned_abs() keeps the minus sign out of the digits, so it never lands in the middle of a group — the sign is reattached as a single character at the end. i128 is wide enough that the function covers every integer type in the language; the last line above is larger than u64 can hold.

The separator is whatever you push: a space for European-style 1 234 567, an underscore matching Rust’s own 1_048_576 literal, one call away.

Floats: group the integer part, keep the fraction

Section titled “Floats: group the integer part, keep the fraction”

The same group works for floats once the number is in fixed-point form — format it, then treat the two sides of the decimal point differently:

1,234,567.89
-1,234,567.5
42.00

The {:.*} spec takes the decimal count from an argument, which is why one function handles any number of places; How to round a float to a number of decimals covers what that formatting does to the stored value. Comma grouping applies to the integer side only — nobody reads 1,234,567.891 with commas in the fraction. places: 0 produces no decimal point at all, and the split_once returning None is the branch that handles it.

Because the helper returns a String, every ordinary format flag applies to the finished string — this is a right-aligned column:

7
42
1,999
1,048,576

The commas count toward the width, so a column of grouped numbers lines up the same way an ungrouped one does. Fill, alignment and precision for other cases are in How to format strings with println and format — that page covers the rest of the spec; the comma is simply not in it.

If the grouping you need is not fixed comma-and-three — locale-specific separators, Indian digit grouping — the standard library has nothing, and the num-format crate is where that lives. For the common case, the helper above is the whole answer.