Question

Check Whether a String Contains a Substring in Rust

ruststring

Question

How can I check whether a Rust string contains a substring? In Python, I would use the in operator:

a = "abcd"
if "bc" in a:
    do_something()

I tried the following Rust code:

let a = "abcd";

if "bc" in a {
    do_something();
}

However, Rust reports expected '{', found 'in'. What is the idiomatic Rust syntax for testing whether one string contains another, and why is the compiler suggesting a block?

Short Answer

Rust does not use Python's in operator for substring checks. Use the contains method from the str type instead:

if a.contains("bc") {
    do_something();
}

By the end, you will know how contains works with string slices and String values, how to write clear conditional checks, and when to use related search methods.

Concept

Rust checks for a substring with str::contains:

let a = "abcd";

if a.contains("bc") {
    println!("Found it!");
}

contains returns a bool:

  • true if the text occurs anywhere in the string
  • false if it does not occur

The method is available on str, Rust's string-slice type. A string literal such as "abcd" has type &str, so it can call contains directly. A String can also call it because Rust automatically borrows it as a string slice when needed.

Rust deliberately has no general Python-style in operator for this operation. Instead, methods make the operation explicit: contains describes exactly what the code is checking.

The compiler's block suggestion is not the solution. After parsing if "bc", Rust expects the body block beginning with {. It does not recognize in as part of an if condition, so its generic parser recovery advice is misleading for this intent.

Mental Model

Think of a string as a line of text and a substring as a short phrase you are trying to spot with a highlighter.

Text:       a b c d
Search for:   b c

a.contains("bc") asks: “Can I find this exact sequence of characters somewhere in this text?” The answer is a simple yes (true) or no (false), which makes it suitable for an if statement.

The method does not need to tell you where it found the text. It only answers whether the text exists. If you need the position, use find instead.

Take Quiz

Syntax and Examples

The basic form is:

text.contains(pattern)

For a normal substring search, pass a string slice such as "rust".

fn main() {
    let language = "Rust programming";

    if language.contains("Rust") {
        println!("The language name was found.");
    }

    if !language.contains("Python") {
        println!("Python was not found.");
    }
}

contains("Rust") evaluates to true, so the first message is printed. The ! operator means “not,” so the second condition is also true.

It works with an owned String too:

fn main() {
    let filename = String::from("report-2025.csv");

    if filename.contains(".csv") {
        println!("This looks like a CSV file.");
    }
}

You usually do not need to convert a String manually with as_str() just to call contains. Rust performs the required borrowing automatically.

Step by Step Execution

Consider this program:

fn main() {
    let a = "abcd";
    let search_text = "bc";

    if a.contains(search_text) {
        println!("Match found");
    }
}

Execution proceeds as follows:

  1. a refers to the string slice "abcd".
  2. search_text refers to the string slice "bc".
  3. a.contains(search_text) searches a for the exact consecutive characters b followed by c.
  4. The sequence occurs in "abcd", so contains returns true.
  5. Because the if condition is true, Rust runs the block and prints Match found.

If search_text were "bd", contains would return false. The characters exist in a, but they are not adjacent, so "bd" is not a substring of "abcd".

Real World Use Cases

Substring checks appear in many everyday Rust programs:

  • File handling: Check whether a filename contains a marker such as "draft" before publishing it.
  • Log processing: Find lines containing "ERROR" or "timeout".
  • HTTP handlers: Check whether a request header contains a required directive.
  • Command-line tools: Search text files for a user-provided query.
  • Validation: Reject a username containing a prohibited word.
  • Data imports: Detect whether a CSV header contains a required column name.

Example: filtering log lines.

let line = "2025-03-08 ERROR database connection failed";

if line.contains("ERROR") {
    eprintln!("An error log entry needs attention: {line}");
}
Take Quiz

Real Codebase Usage

In production code, contains is often used as a readable condition, especially for quick filtering and validation.

Guard clauses

Return early when input contains prohibited content:

fn validate_comment(comment: &str) -> Result<(), String> {
    if comment.contains("<script") {
        return Err("HTML script tags are not allowed".to_owned());
    }

    Ok(())
}

Filtering a collection

Use filter when selecting items that contain text:

let files = ["notes.txt", "todo.txt", "image.png"];

let text_files: Vec<&str> = files
    .iter()
    .copied()
    .filter(|name| name.contains(".txt"))
    .collect();

assert_eq!(text_files, vec!["notes.txt", "todo.txt"]);

Configuration and feature checks

fn is_debug_mode(flags: &str) -> bool {
    flags.contains("debug")
}

For structured values, prefer parsing over substring matching. For example, use std::path::Path::extension() to check a file extension rather than checking whether a filename merely contains ".csv".

Common Mistakes

Using Python syntax

This is invalid Rust:

// Does not compile
if "bc" in a {
    println!("Found");
}

Use a method call instead:

if a.contains("bc") {
    println!("Found");
}

Expecting case-insensitive matching

contains is case-sensitive:

assert!("Rust".contains("Rust"));
assert!(!"Rust".contains("rust"));

For simple ASCII-oriented case-insensitive checks, normalize both values:

let text = "Learn Rust";
let query = "RUST";

if text.to_lowercase().contains(&query.to_lowercase()) {
    println!("Match");
}

This creates new String values. Unicode case-insensitive matching has additional language-specific considerations, so avoid assuming lowercase conversion is always equivalent to human-language case folding.

Confusing a substring with separate characters

assert!(!"abcd".contains("bd"));

Although b and d both appear, "bd" does not appear as one continuous sequence.

Assuming contains returns an index

contains returns bool, not a position. Use find if you need the byte index:

assert_eq!("abcd".find("bc"), Some(1));

Using contains for an exact match

// This is true for "administrator" too.
let is_admin = username.contains("admin");

Use equality when the complete value must match:

let is_admin = username == "admin";

Treating contains(".csv") as reliable extension validation

A name like report.csv.backup contains ".csv" but is not necessarily a CSV file. Use path-aware APIs when working with filesystem paths.

Comparisons

NeedRecommended Rust operationExampleResult
Test whether text occurs anywherecontainstext.contains("cat")bool
Find the first occurrencefindtext.find("cat")Option<usize>
Test the beginningstarts_withtext.starts_with("https://")bool
Test the endends_withtext.ends_with(".rs")bool
Split around a delimitersplittext.split(",")iterator
Check complete equality==text == "cat"bool

contains is best when location does not matter. Choose find when you need to slice, report, or otherwise use the match location.

Also note that string indices returned by find are byte indices, because UTF-8 characters can use more than one byte. Do not assume every numeric byte position is a valid character boundary for arbitrary strings.

Take Quiz

Cheat Sheet

// Basic substring test
let found = text.contains("needle");

// Use in a condition
if text.contains("needle") {
    println!("Found");
}

// Negative test
if !text.contains("needle") {
    println!("Not found");
}

// String literals (&str)
let text = "hello world";
assert!(text.contains("world"));

// Owned strings (String)
let text = String::from("hello world");
assert!(text.contains("world"));

// Related methods
assert!("hello".starts_with("he"));
assert!("hello".ends_with("lo"));
assert_eq!("hello".find("ll"), Some(2));

Key rules:

  • Use text.contains(pattern), not pattern in text.
  • Matching is case-sensitive.
  • A match is consecutive text, not merely characters that appear somewhere.
  • contains returns bool.
  • An empty substring is contained in every string: "abc".contains("") is true.

FAQ

How do I check if a string contains another string in Rust?

Call contains:

if text.contains("search term") {
    // match
}

Does contains work with String and &str?

Yes. It works on string slices (&str) and can be called directly on String values through automatic borrowing.

Is Rust contains case-sensitive?

Yes. "Rust".contains("rust") is false. Normalize or use a purpose-built comparison strategy if case should not matter.

Why does Rust reject the in operator?

Rust does not define Python-style in syntax for substring membership. After an if condition, its parser expects a { block, which is why the resulting syntax error mentions {.

How do I get the position of a substring in Rust?

Use find:

let position = "abcd".find("bc");
assert_eq!(position, Some(1));

Does contains use regular expressions?

No. contains("a.b") searches for the literal characters a, ., and b. Use a regex library when you specifically need pattern matching rules.

What happens when I search for an empty string?

It returns true, because an empty sequence exists at every boundary in a string:

assert!("abc".contains(""));
Take Quiz

Mini Project

Description

Build a small log scanner that examines application log entries and reports entries containing a search term. This mirrors a common command-line and monitoring task: quickly locating warnings or errors in text output.

Goal

Print every log entry that contains a user-selected search term, along with the number of matching entries.

Requirements

Use a collection containing at least five log messages. Store the search term in a variable. Check each log message with contains. Print matching log messages. Print the total number of matches.

Take Quiz

Keep learning

Related questions

View more