Question
I have a string and need to scan for every occurrence of "foo", then read all the text that follows it until a second " character. I thought I would need to iterate through the string character by character to search for it, because I believed Rust strings did not have a contains function.
How can this be done in Rust?
Update: Rust's &str does provide contains() and find() methods.
Short Answer
By the end of this page, you will understand how string iteration works in Rust, why strings are not indexed by character, and when iterating with chars() is useful. You will also learn that many text-search tasks are better solved with built-in &str methods such as contains(), find(), match_indices(), and slicing carefully at valid UTF-8 boundaries.
Concept
Rust strings are a little different from strings in many other languages.
A Rust string slice, &str, stores UTF-8 text. UTF-8 uses a variable number of bytes per character, so Rust does not allow direct indexing like s[0] for characters. That is important because a single visible character may take more than one byte.
Because of that, Rust gives you several safe ways to work with strings:
chars()to iterate over Unicode scalar valuesbytes()to iterate over raw bytescontains()to check whether text exists inside a stringfind()to get the byte position of a matchmatch_indices()to find all matches- string slicing like
&s[start..end], but only at valid UTF-8 boundaries
For the specific problem in the question, iterating character by character is often not the best first approach. If you want to find every occurrence of a substring like "foo", Rust already provides substring search methods.
Why this matters in real programming:
- Text parsing is common in log files, config files, CSV-like formats, and templates.
- Using built-in string methods usually makes code shorter and less error-prone.
- Understanding UTF-8 boundaries helps you avoid panics when slicing strings.
So the key lesson is:
Mental Model
Think of a Rust string like a box of encoded text bytes, not a simple row of fixed-size letters.
If you want to inspect each letter one by one, you ask Rust to decode the string using chars().
If you want to search for a word like "foo", you usually do not need to open the box one character at a time. Instead, you use built-in search tools like find() or match_indices() that are designed for that job.
So:
chars()= reading the text one character at a timebytes()= inspecting the raw storagefind()/contains()= asking Rust to search the text for you
Syntax and Examples
Iterating over characters
fn main() {
let text = "foo \"hello\" bar";
for ch in text.chars() {
println!("{}", ch);
}
}
This prints each character in the string, one at a time.
Checking whether a string contains text
fn main() {
let text = "foo \"hello\" bar";
if text.contains("foo") {
println!("Found foo");
}
}
Use contains() when you only need to know whether the substring exists.
Finding the first occurrence
fn main() {
let text = "abc foo \"hello\" xyz";
if let Some(index) = text.() {
(, index);
}
}
Step by Step Execution
Consider this example:
fn main() {
let text = "foo \"cat\" and foo \"dog\"";
for (start, _) in text.match_indices("foo \"") {
let content_start = start + "foo \"".len();
if let Some(end_offset) = text[content_start..].find('"') {
let content_end = content_start + end_offset;
let value = &text[content_start..content_end];
println!("Found: {}", value);
}
}
}
Step-by-step
-
textis set to:foo "cat" and foo "dog" -
match_indices("foo \"")looks for every occurrence of the exact substringfoo ". -
On the first match:
Real World Use Cases
String iteration and substring searching show up in many real programs.
Parsing quoted values
You might scan text like:
name="Alice" role="admin"
and extract the value inside quotes.
Processing log files
Applications often search logs for markers such as:
ERRORWARNuser="..."
Reading configuration-like text
Simple parsers often need to locate prefixes and capture the text that follows.
Token scanning
A script may walk through characters when checking:
- punctuation
- delimiters
- quoted strings
- escaped characters
Input validation
You may search user input for forbidden words, expected prefixes, or structured patterns before storing data.
Real Codebase Usage
In real Rust codebases, developers usually prefer built-in string operations over manual character scanning when possible.
Common patterns
Use search methods first
If you are looking for a fixed substring:
contains()for yes/no checksfind()for the first matchrfind()for the last matchmatch_indices()for all matches
Use guard clauses
fn extract_after_foo(text: &str) -> Option<&str> {
let start = text.find("foo \"")? + "foo \"".len();
let end = text[start..].find('"')? + start;
Some(&text[start..end])
}
This uses ? with Option for clean early returns.
Avoid unnecessary allocation
Common Mistakes
1. Trying to index a string like an array
Broken code:
let s = "hello";
let first = s[0];
This does not compile because Rust strings are UTF-8 and are not directly indexable by character.
Avoid this by using:
s.chars().next()for the first characterfind()for substring search
2. Using character iteration for substring search when a string method is simpler
Broken approach:
for ch in text.chars() {
// manually trying to detect "foo"
}
This is possible, but often more complicated than needed.
Better:
if text.contains("foo") {
println!("Found it");
}
3. Forgetting that indexes are byte indexes
= ;
(, s.());
Comparisons
| Concept | Best for | Returns | Notes |
|---|---|---|---|
chars() | Per-character processing | char iterator | Good for parsing character rules |
bytes() | Raw byte processing | u8 iterator | Useful for low-level work |
contains() | Check whether text exists | bool | Simplest existence check |
find() | First occurrence | Option<usize> | Returns byte index |
Cheat Sheet
Quick reference
Iterate over characters
for ch in text.chars() {
println!("{}", ch);
}
Iterate with positions
for (i, ch) in text.char_indices() {
println!("{}: {}", i, ch);
}
Check for a substring
text.contains("foo")
Find first match
text.find("foo")
Find all matches
text.match_indices("foo")
Safe extraction pattern
if let Some(start) = text.() {
= start + .();
(end_offset) = text[content_start..].() {
= content_start + end_offset;
= &text[content_start..end];
(, value);
}
}
FAQ
Does Rust really have a contains() method for strings?
Yes. &str has a contains() method, and it can be used through String as well by borrowing it as a string slice.
Why can't I use s[0] on a Rust string?
Because Rust strings are UTF-8, and one character may use multiple bytes. Direct indexing would be ambiguous and potentially unsafe.
When should I use chars() in Rust?
Use chars() when your logic depends on processing one character at a time, such as counting letters or parsing delimiters.
What does find() return in Rust?
It returns Option<usize>, where the usize is the byte index of the first match.
How do I find every occurrence of a substring in Rust?
Use match_indices() to iterate over all matches and their positions.
Is find() enough for parsing quoted text?
For simple cases, yes. For more complex formats with escapes or nested structures, you may need a more detailed parser.
Should I use or for searching?
Mini Project
Description
Build a small Rust program that scans a line of text and extracts every value that appears after foo " and before the next ". This demonstrates practical string searching, repeated matching, and safe slicing with &str.
Goal
Write a Rust program that collects and prints all quoted values following foo in a string.
Requirements
- Read from a hardcoded input string containing multiple
foo "..."segments. - Find every occurrence of
foo ". - Extract the text until the next closing
". - Print each extracted value.
- Skip incomplete matches that do not have a closing quote.
Keep learning
Related questions
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Associated Types vs Generic Type Parameters in Rust: When to Use Each
Learn when to use associated types vs generic parameters in Rust traits, with clear rules, examples, and practical API design advice.
Can a Struct Extend Another Struct in Rust? Composition vs Inheritance
Learn how Rust handles struct reuse without inheritance, using composition, traits, and wrapper structs with practical examples.