There are many scenarios where only a part of a collection is required (e.g., extracting part of a string by searching it, tokenizing a string, etc.) The required index can be retrieved with the help of a function and can then be used later on. However, the index may become invalid if the string changes.
For example, consider the function get_index
which finds the index of the first occurrence of the letter Q
in a string or returns the string’s length otherwise. The main()
function then prints the string up to this index:
fn get_index(s: &String) -> usize {// Convert String to array of bytes:let bytes = s.as_bytes();// Iterate over the whole array:for (i, &item) in bytes.iter().enumerate() {if item == b'Q' {return i;}}s.len()}fn main() {let mut sequence = String::from("opQrs");let index = get_index(&sequence);// Print before clearing string:println!("Index of Q before clearing the string: {}", index);// Clear the string:sequence.clear();println!("String cleared");// Print sequence upto Q:let bytes = sequence.as_bytes();for (i, &item) in bytes.iter().enumerate() {// Compiler still considers 'index' to be valid:if i <= index {println!("{}", item);}}}
A problem with this implementation is that the compiler won’t be able to detect an error when the string is erased from the memory or is changed (as in line 20).
A string slice is a portion of a string that is specified using a range of indices.
A string slice can be extracted using the following syntax:
// Get index 2 of my_string:
&my_string[2..3];
// Get my_string from index 1 to 4:
&my_string[1..5];
// Get my_string from index 0 to 3:
&my_string[..4];
// Get my_string from index 2 to the last index:
&my_string[2..];
// Get all of my_string:
&my_string[..];
&str
, which is an immutable reference, is the data type of a string slice. Therefore, a function that returns a string slice will be similar to the following:fn foo() -> &str {
// Code
}
From the memory’s perspective (where the string slice is stored), a string slice is a data structure that stores a pointer to the starting index and its length.
Let’s try to change the code presented earlier. A helper()
function will now return a string slice that main()
will print after the string gets changed:
fn main() {let mut sequence = String::from("opQrs");let part = helper(&sequence); //--------+// |// Try to clear the string: | Lifetime of immutablesequence.clear(); // | reference to the// | string slice// Compiler detects the error: |println!("String upto Q = {}", part); //+}fn helper(s: &String) -> &str {// Convert String to array of bytes:let bytes = s.as_bytes();// Iterate over the whole array:for (i, &item) in bytes.iter().enumerate() {if item == b'Q' {return &s[..i+1];}}// Return the whole string instead of the length:&s[..]}
Recall the rules for borrowing and using references. Since the clear()
method uses a mutable reference, the compiler won’t allow the string to be changed (line 6) before the last usage of the string slice (line 9). The more responsibility the compiler takes, the fewer bugs we will face.
Free Resources