🎓 A1Learner
🔍

Rust Text Patterns

A pattern is the thing a search method looks for in text. A pattern can be quoted text, one character, a set of characters, or a test that a character passes.

Three kinds of pattern, one method

contains accepts text, a set of chars, or a test — the call shape never changes:

println!("{}", "rust is fun".contains("is"));
println!("{}", "hello".contains(&['x', 'l'][..]));
println!("{}", "abc1".contains(char::is_numeric));

find gives a byte position

The é in "héllo" takes two bytes, so the first l sits at byte 3, not letter 2:

println!("{:?}", "héllo".find('l'));
println!("{:?}", "héllo".find('z'));

A test can be the pattern

Hand a char test where a pattern is asked for, and the first character that passes it is the match:

println!("{:?}", "error 42".find(char::is_numeric));
println!("{}", "9lives".starts_with(char::is_numeric));

matches finds them all

matches collects every match. match_indices adds the byte position of each:

println!("an count = {}", "banana".matches("an").count());
let pairs: Vec<(usize, &str)> = "héllo".match_indices('l').collect();
println!("{:?}", pairs);

starts_with and ends_with take patterns too

The same idea works at the edges, with any pattern kind:

println!("{}", "photo.png".ends_with(".png"));
println!("{}", "file.rs".starts_with(&['f', 'x'][..]));

Example

fn main() {
    let line = "error at line 42: disk full";
    println!("has error = {}", line.contains("error"));
    println!("first digit at byte = {:?}", line.find(char::is_numeric));
    println!("colon at byte = {:?}", line.find(':'));
    let vowels = "rust is fun".matches(&['a', 'e', 'i', 'o', 'u'][..]).count();
    println!("vowels = {}", vowels);
    let pairs: Vec<(usize, &str)> = "héllo".match_indices('l').collect();
    println!("l pairs = {:?}", pairs);
}

Key points

  • Whatever its kind, a pattern answers one question: does this piece of text match?
  • A pattern can be a &str: the whole text must appear in a row.
  • A pattern can be a char, matching that one character.
  • A pattern can be a set of chars like &['a', 'e']: any one of them.
  • A pattern can be a test like char::is_numeric: any character the test accepts.
  • Methods that take a pattern: contains, starts_with, ends_with, find, rfind, matches, match_indices, split and its family, replace, and trim_matches
  • find hands back the BYTE position of the first match as an Option — count it in bytes, not letters
  • rfind searches from the end and finds the last match
  • matches walks every match.
  • match_indices walks each match with its byte position.
  • A plain pattern matches fixed text or one character at a time.
  • A plain pattern cannot describe a shape, like "four digits, a dash, two digits".

In this example, one log line is searched four ways. contains uses a text pattern and answers true. find with the test char::is_numeric locates the first digit: byte 14, where the 42 begins. find with a char pattern locates the colon at byte 16. Then matches with a set-of-chars pattern counts the vowels in "rust is fun": u, i, u — three. Last, match_indices lists both l letters of "héllo" with their byte positions, 3 and 4. The first sits at byte 3 because é takes bytes 1 and 2. One idea, four methods.

Note

The pattern type is never written by hand.

The Pattern trait behind it cannot even be named on stable Rust yet.

A char, a set, a text or a test can be passed wherever a method asks for a pattern.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What does "hello".find(&['x', 'l'][..]) return?