Skip to content
Select theme

How to check whether a string contains a substring

Short answer: str::contains answers whether a substring appears anywhere in a string; starts_with and ends_with check the two ends. All three borrow the pattern instead of allocating, and all three are case-sensitive, so a case-insensitive search folds both sides to the same case first. Unicode case folding is not clean in either direction, which is the one place this gets subtle.

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

false
true
false
true

contains scans the whole string; the other two look at one end. Each returns bool and borrows, so nothing is allocated and the haystack is untouched. If you also need where the match is, str::find hands back an Option<usize>: Some(4) for where "Lovelace" starts in the text above, None when the substring is absent.

The second argument is anything implementing Pattern, which covers the &str you expected plus four more spellings:

true
true
true
false
true
true

An array of chars means “any of these characters”, not that sequence: "Ada".contains(['d', 'A']) is true even though the string contains no dA anywhere, because both letters appear. The closure runs per character, so it cannot match a chunk longer than one. A String needle needs the &, because the trait is implemented for the borrowed forms:

error[E0277]: the trait bound `String: Pattern` is not satisfied
--> src/main.rs:4:34
|
4 | println!("{}", text.contains(needle));
| -------- ^^^^^^ the trait `Pattern` is not implemented for `String`
| |
| required by a bound introduced by this call
|
= note: required for `String` to implement `Pattern`
note: required by a bound in `core::str::<impl str>::contains`
--> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/str/mod.rs:1383:4
help: consider borrowing here
|
4 | println!("{}", text.contains(&needle));
| +
For more information about this error, try `rustc --explain E0277`.

To ask about several substrings at once, iterate: ["Grace", "Lovelace"].iter().any(|p| text.contains(p)).

One trap sits in the pattern itself. An empty needle is contained in everything, so "anything".contains("") is true, and so are starts_with("") and ends_with(""). When the needle came from a split or from user input, check needle.is_empty() before believing a true.

std has no contains_ignore_case, and the compiler is blunt about it:

error[E0599]: no method named `contains_ignore_case` found for reference `&str` in the current scope
--> src/main.rs:3:25
|
3 | println!("{}", text.contains_ignore_case("lovelace"));
| ^^^^^^^^^^^^^^^^^^^^ method not found in `&str`
For more information about this error, try `rustc --explain E0599`.

Fold both sides to the same case and search again:

false
true
true
true

to_lowercase allocates a String for the haystack, so a search inside a loop over many lines pays that per line; to_ascii_lowercase is the cheaper ASCII-only spelling. eq_ignore_ascii_case compares two whole strings without allocating, but it cannot answer “contains”.

Unicode case mapping is not a pair of clean inverses, and both directions lose something:

false
true
"i\u{307}stanbul"
false
false

ß uppercases to SS, so uppercasing both sides matches Straße against STRASSE where lowercasing both sides misses it. İ goes the other way: its lowercase is an i with a combining dot above, which is why the {:?} line shows the escape rather than a bare letter, and uppercasing does not rescue it either, because İ stays İ while i becomes I. Checking both folds covers ß; nothing in std covers İ. For text that mixes them, this is the point to stop folding by hand and use a crate that implements real case folding.