Skip to content
Select theme

How to trim whitespace and strip characters

Short answer: str::trim removes whitespace from both ends and returns a slice of what is left; trim_start and trim_end do one side each. For characters that are not whitespace, trim_matches takes a char, an array of chars, or a closure. The detail worth knowing: \r is whitespace, so trim() clears the stray carriage return a Windows line ending leaves behind.

From a fresh project — cargo new trim-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.

"padded text"
"padded text \n "
" \t padded text"

All three return &str — a slice of the same string, nothing allocated, the original untouched. Spaces, tabs and newlines come off both ends; everything else stays.

trim follows char::is_whitespace, the Unicode White_Space property, so the non-breaking space and the ideographic space go with the obvious ones. The zero-width space does not: it looks like a space and is not one.

"text"
"text"
"\u{200b}text\u{200b}"
true
false

trim_matches(char::is_whitespace) is the same thing written out longhand. {:?} is what makes the invisible visible: Debug escapes the bytes that {} prints raw.

trim_matches takes a pattern instead of the whitespace set: a single char, an array of chars, or a closure that decides per character. Each match comes off both ends, repeatedly, until neither end matches any more.

"usr/local/bin"
"a,b"
"b"
"a + b"

Separators inside the string survive — only the ends are trimmed. trim_start_matches and trim_end_matches keep the same patterns for one end only.

A &str pattern is refused, even a one-character one. Trimming from both ends needs a searcher that runs backwards, and the searcher for multi-character patterns does not:

error[E0277]: the trait bound `for<'a> StrSearcher<'a, '_>: DoubleEndedSearcher<'a>` is not satisfied
--> src/main.rs:3:40
|
3 | println!("{:?}", text.trim_matches(" "));
| ------------ ^^^ the nightly-only, unstable trait `for<'a> DoubleEndedSearcher<'a>` is not implemented for `StrSearcher<'a, '_>`
| |
| required by a bound introduced by this call
|
= help: the following other types implement trait `DoubleEndedSearcher<'a>`:
CharArrayRefSearcher<'a, 'b, N>
CharArraySearcher<'a, N>
CharPredicateSearcher<'a, F>
CharSearcher<'a>
CharSliceSearcher<'a, 'b>
note: required by a bound in `core::str::<impl str>::trim_matches`
--> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/str/mod.rs:2378:4
For more information about this error, try `rustc --explain E0277`.

Write ' ' for a single character and [' ', '\t'] for a set. A chunk longer than one character needs a loop around strip_prefix and strip_suffix.

Text often arrives as one blob — a file read into a String, a response body — and splitting it on '\n' leaves half of a Windows line ending behind:

"alpha\r"
false
true
"alpha"
"beta"

The first piece is "alpha\r", not "alpha", so the comparison is false. The \r is what makes this hard to spot: a carriage return moves the terminal’s cursor back to the start of the line, so println!("{first}") prints something that looks clean while the byte is still there. A trailing \r is enough to make "42\r".parse::<i32>() return Err. trim() removes it, because \r is whitespace. The newline is the other half:

"alpha\r"
"alpha"

trim_end_matches('\n') takes the newline and leaves the carriage return sitting at the end; trim_end() takes both. And lines() splits on \r\n as well as \n, which is why the block above shows clean lines — for a whole file, that is the simpler fix.

trim borrows. Asking for an owned String without saying so is a type error, and the compiler’s own suggestion is the fix:

error[E0308]: mismatched types
--> src/main.rs:3:27
|
3 | let trimmed: String = text.trim();
| ------ ^^^^^^^^^^^ expected `String`, found `&str`
| |
| expected due to this
|
help: try using a conversion method
|
3 | let trimmed: String = text.trim().to_string();
| ++++++++++++
For more information about this error, try `rustc --explain E0308`.

Add .to_string() when the trimmed text has to outlive the string it came from; keep the &str when it does not. Which conversion to use has the full comparison.