Searching for a substring
To find where a piece of text occurs inside a string, use the method find. It looks for the first occurrence of the pattern you give it and reports the position where it starts:
let sentence = "Hello World";
let idx = sentence.find("World");
println!("{:?}", idx);
Some(6)
The text World starts at position 6, counting from 0, so the result is Some(6). The {:?} in the format string prints a value in its debug form, which is how an Option is shown. A plain {} would not compile for it.
The result is an Option
The text might not be there at all, and Rust does not return a magic number such as -1 for that case. find returns an Option<usize>:
Some(i)when the pattern occurs;iis the byte position of its first character.Nonewhen it doesn't.
The compiler makes you deal with both cases before you can use the number, which prevents the classic bug of using -1 as a real position. Here are the usual ways to handle the result:
let sentence = "Hello World";
if sentence.find("xyz").is_none() {
println!("not found");
}
match sentence.find("World") {
Some(i) => println!("found at {}", i),
None => println!("missing"),
}
if let Some(i) = sentence.find("llo") {
println!("{}", &sentence[i..]);
}
not found
found at 6
llo World
Use is_some() or is_none() for a yes-or-no test, match or if let to use the position, and unwrap_or(0) to supply a default. Calling unwrap() on a None panics, so reserve it for cases where the text is certain to be there. If you only need to know whether the text is present, sentence.contains("World") is simpler.
What can be searched for
The pattern can be a string slice, a single char, or a closure that tests one character. Searching is case-sensitive.
sentence.find("World")givesSome(6)andsentence.find("world")givesNone.sentence.find('o')givesSome(4), the firsto.sentence.find(|c: char| c.is_uppercase())givesSome(0).sentence.find("")givesSome(0), since an empty pattern matches at the start.
Last occurrence and later occurrences
rfind searches from the end, so it returns the position of the last occurrence: sentence.rfind('o') is Some(7), while find('o') is Some(4).
To search from a given position, slice first: sentence[5..].find('o'). The result is relative to the slice, so it is Some(2), not the position in the full string. Add the start to get the real position:
let start = 5;
let abs = sentence[start..].find('o').map(|i| i + start);
println!("{:?}", abs);
Some(7)
The map adds the start only when there is a Some, and leaves None as it is.
Positions are byte offsets
The number inside Some is a position in bytes, which is the kind of number slicing takes, so &s[i..] is always valid for a found i. For ASCII text it equals the character count. In "héllo wörld" the letter é takes two bytes, so find("w") gives Some(7), while chars().position(|c| c == 'w') gives Some(6), a character position. Use the form that matches what you do next.
The lesson's exercise
The starter code declares sentence as "Hello World". Below it, declare idx with let and assign it the result of find("World"):
let sentence = "Hello World";
let idx = sentence.find("World");
Here idx is an Option<usize> holding Some(6), not the bare number 6. To get the number you would write idx.unwrap() or match on it.
Key takeaways
s.find(pattern)returnsOption<usize>:Some(byte position)of the first match, orNone.rfindgives the last match.- Searching a slice such as
s[start..]returns a position relative to the slice. - Handle the
Optionwithmatch,if let,is_some()orunwrap_or;unwrap()onNonepanics.
