# textwrap wraps text with an optimal-fit algorithm rather than filling each line in turn

> textwrap is a Rust crate for wrapping and indenting text, with a look-ahead line-breaking algorithm behind a Cargo feature flag, optional TeX-quality hyphenation for about 70 languages and support for no_std with an allocator. It suits a developer formatting terminal output or generating proportional text, and the difference between its two line-breaking modes is visible in the output.

**mgeisler/textwrap** — An efficient and powerful Rust library for word wrapping text.

- Repository: https://github.com/mgeisler/textwrap
- Stars: 527 · Forks: 60
- Language: Rust
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/mgeisler-textwrap

## What it is, and where it is usually used

Textwrap is a library for wrapping and indenting text, and it is most often used by command-line programs to format dynamic output nicely so it looks good in a terminal.

That is the primary audience and it explains the design priorities. A CLI tool prints lines of unknown length, often containing identifiers, paths and messages it did not write, and the result has to look deliberate rather than ragged.

The README also points at a second use case that is less obvious. You can use Textwrap to wrap text set in a proportional font, such as text used to generate PDF files, or drawn on an HTML5 canvas using WebAssembly.

That second case is where the library's algorithm stops being a nicety. In a terminal, a greedy line break looks slightly uneven. In proportional text, the same greedy break can leave a visibly short first line that changes how the paragraph reads, and the crate's look-ahead algorithm exists for exactly that reason.

The crate is on crates.io as textwrap, the API documentation is on docs.rs, and the repository carries a fuzz/ directory, benchmarks/, a CHANGELOG.md, an AGENTS.md, rustfmt.toml and dprint.json. The current version is 0.16.4 with rust-version 1.88 as the minimum supported Rust version.

## The difference between the two wrapping modes

The README's opening example is built to make a point, and it is the clearest explanation of what the crate does differently.

```rust
#[cfg(feature = "smawk")] {
let text = "textwrap: an efficient and powerful library for wrapping text.";
assert_eq!(
    textwrap::wrap(text, 28),
    vec![
        "textwrap: an efficient",
        "and powerful library for",
        "wrapping text.",
    ]
);
}
```

Sharp-eyed readers will notice that the first line is 22 columns wide, and the README asks why the word and is put in the second line when there is space for it in the first.

The answer is that textwrap does not just wrap text one line at a time. It uses an optimal-fit algorithm which looks ahead and chooses line breaks that minimize the gaps left at the ends of lines.

The greedy alternative is shown in the same document.

```rust
#[cfg(not(feature = "smawk"))] {
let text = "textwrap: an efficient and powerful library for wrapping text.";
assert_eq!(
    textwrap::wrap(text, 28),
    vec![
        "textwrap: an efficient and",
        "powerful library for",
        "wrapping text.",
    ]
);
}
```

Here the first line is filled and the second is shorter, and the README calls the text more ragged.

So the choice is not cosmetic. A greedy wrapper packs the current line and moves on, which is what most simple wrappers do. The look-ahead wrapper treats the paragraph as a whole and solves for the set of breaks that leaves the least unused space overall, which can pull a word down to the next line to avoid a big gap above it.

The algorithm is selectable at runtime through `Options::wrap_algorithm`, which means you can switch between them at runtime rather than at compile time, and the Cargo feature named for the underlying algorithm, `smawk`, controls whether the optimal-fit code is compiled in at all.

## Hyphenation, and the cost of it

With the `hyphenation` Cargo feature enabled, the crate gets support for automatic hyphenation for about 70 languages using high-quality TeX hyphenation patterns.

The API requires two steps: the program must load the hyphenation patterns, and it must configure `Options::word_splitter` to use them.

```rust
#[cfg(feature = "hyphenation")] {
use hyphenation::{Language, Load, Standard};
use textwrap::{fill, Options, WordSplitter};

let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
let options = textwrap::Options::new(28).word_splitter(WordSplitter::Hyphenation(dictionary));
let text = "textwrap: an efficient and powerful library for wrapping text.";

assert_eq!(
    textwrap::wrap(text, &options),
    vec![
        "textwrap: an efficient and",
        "powerful library for wrap-",
        "ping text."
    ]
);
}
```

Note that the output differs from the plain wrap: with hyphenation the first line takes the full width because a word can now be split, and the second line breaks inside wrapping.

The cost is stated plainly. The US-English patterns are embedded when you enable the feature, they are licensed under a permissive licence, and they take up about 88 KB in your binary. If you need hyphenation for other languages you have to download a precompiled .bincode file and load it yourself, with the hyphenation documentation covering the details.

So the feature is two things in one: an easy path for English, where the patterns are compiled in, and a manual path for every other language, where you fetch a dictionary at build time or runtime and load it yourself.

## Cargo features, and the no_std story

The feature list in the Cargo.toml is the real specification of the crate, and it is worth reading because it shows what you pay for.

The default features are std, unicode-linebreak, unicode-width and smawk. The other features are unicode-linebreak-complex, terminal_size, hyphenation, and std on its own.

By default, the crate enables word wrapping with support for Unicode strings, and the README notes that extra features can be enabled with Cargo features and that the Unicode support can be disabled if needed, so you only pay for the features you actually use.

The dependency list explains the feature flags. smawk is the linear-time optimal-fit algorithm, terminal_size detects the terminal width, unicode-width computes character display widths, hyphenation brings the TeX patterns, and icu_segmenter provides line breaking, with unicode-linebreak enabling it and unicode-linebreak-complex enabling the auto mode of the segmenter.

There is a subtlety worth knowing. The std feature itself depends on unicode-width with the default feature on, so disabling std does not fully detach the crate from unicode-width.

For embedded targets, Textwrap supports no_std environments with an allocator, and you can enable unicode-width for accurate character widths on those targets. The README points at the Cargo Features section of the crate documentation for the full list and their impact on binary size, and the repository has an examples/binary-sizes/ directory for measuring that.

The crate also has a companion, textwrap-macros, which the README recommends if your strings are known at compile time, since wrapping them at compile time removes the runtime cost entirely.

## The examples, including a terminal demo you can actually use

The library comes with a collection of small example programs, and the README points at one in particular as the way to see the features in motion.

The demo program is examples/interactive.rs, which demonstrates most of the available features: you can enter text and adjust the width at which it is wrapped interactively, and you can adjust the `Options` used to see the effect of different `WordSplitter`s and wrap algorithms.

```sh
$ cargo run --example interactive
```

The demo needs a Linux terminal to function, which tells you it depends on terminal control rather than on stdin parsing alone.

That example is the right way to understand the crate, because the visible difference between the algorithms is the whole point of the library and a static assertion in a doc test does not convey it.

The rest of the examples directory shows the range. There is examples/hello_world.rs for the basic case, examples/hyphenation.rs for the hyphenation path, which the Cargo.toml gates behind required-features so it only builds with the feature enabled, examples/termwidth.rs for detecting the terminal width, also feature-gated, examples/debug-words.rs for inspecting how the wrapper splits words, examples/layout.rs, examples/owned-options.rs, and examples/wasm/ which shows wrapping sans-serif, serif and monospace text and is automatically deployed to the project's GitHub Pages site.

The wasm example is the one to look at if you came from the PDF or canvas use case, since it demonstrates that the same wrapping code can produce output for proportional fonts where character width is not a simple count.

## Where textwrap is the wrong choice

Four cases argue against it.

The first is a document that needs typesetting. textwrap breaks lines; it does not lay out a paragraph. There is no justification, no paragraph shaping, no line spacing, no page composition. If the output is going into a PDF or a page with mixed font sizes, you want a typesetting engine, and this crate is one small part of that problem.

The second is a case where the greedy result is fine and the look-ahead costs you. The optimal-fit algorithm needs the whole paragraph in view, and the smawk feature exists to make it linear time, but it is still more work than filling the current line. For a tool that wraps millions of short strings in a loop, the simpler algorithm with the smaller binary is the better trade, and that is a legitimate choice the crate supports by making the feature optional.

The third is a language other than Rust. There is no port to look at here, and while wrapping algorithms are portable, the ecosystem has its own options depending on your runtime.

The alternative in the same direction is a formatting library from your language's ecosystem, or a general text processing library that includes wrapping. The difference in approach is that textwrap's distinguishing feature is the optimal-fit algorithm plus the TeX-quality hyphenation, and that combination is what you would otherwise have to assemble yourself from a word splitter and a line breaker. For a CLI that prints a help message and an error, none of that matters and any word-wrap helper will do. For a tool that renders paragraphs of prose to a proportional font, the look-ahead and the hyphenation are the reason to take this crate rather than write the loop yourself.

## Conclusion

textwrap fits a Rust developer who needs terminal output to look right, or who needs wrapping for proportional text where a greedy line break would be visibly wrong, and the API is small enough to read in one sitting. It does not fit a project that needs a full typesetting engine, since it breaks lines rather than laying out paragraphs. Add `textwrap = "0.16"` to your Cargo.toml, and note that the `smawk` feature is on by default and is what makes the wrapping optimal rather than greedy, so removing it to save binary size changes the output of every line you print.

## FAQ

### What is textwrap?

It is a Rust library for wrapping and indenting text, most often used by command-line programs to format dynamic output nicely. It can also wrap text set in a proportional font for PDF generation or an HTML5 canvas through WebAssembly. The current version is 0.16.4 and the minimum supported Rust version is 1.88.

### How do I add textwrap to a Cargo project?

Add `textwrap = "0.16"` to the dependencies in your Cargo.toml. The default features enable word wrapping with Unicode string support, and extra features are toggled through Cargo features so you only pay for what you use.

### Why does textwrap sometimes leave the first line short?

Because it does not wrap one line at a time. It uses an optimal-fit algorithm that looks ahead and chooses line breaks minimizing the gaps left at the ends of lines, which is controlled by the smawk Cargo feature and can be changed at runtime through Options::wrap_algorithm.

### Does textwrap support hyphenation?

Yes, with the hyphenation Cargo feature, which adds automatic hyphenation for about 70 languages using TeX hyphenation patterns. You load the patterns and configure Options::word_splitter. US-English patterns are embedded and take about 88 KB in your binary, and other languages require downloading a precompiled bincode file yourself.

### Can textwrap be used without the standard library?

Yes. Textwrap supports no_std environments with an allocator, and you can enable unicode-width for accurate character widths on embedded targets. The Cargo Features section of the crate documentation lists the available features and their impact on binary size.

## Sources

- [Issues](https://github.com/mgeisler/textwrap/issues)
- [License: MIT](https://github.com/mgeisler/textwrap/blob/main/LICENSE)
- [mgeisler/textwrap on GitHub](https://github.com/mgeisler/textwrap)
- [README](https://github.com/mgeisler/textwrap/blob/main/README.md)
- [Releases](https://github.com/mgeisler/textwrap/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mgeisler-textwrap
