Slice

  • A slice is a reference to a contiguous (i.e. one after the other) sequence of elements in a collection or in a string.
  • A slice is a kind of reference. Thus, it doesn't have ownership.
  • Unlike the references used when we are borrowing, which is through an existing variable or a temp variable, the slice data structure internally stores the starting position and the length of the slice.
  • You obtain a slice using &[start_index..end_index] where start index is the index of the first item in the slice end_index is one past the last element we want to include in the slice.

String slices

  • A string slice is an immutable reference to a contiguous part of a String (i.e., the String data type).
  • The data type for string slice is &str
fn main() {
    let greeting = String::from("How are you?");
    
    let part1: &str = &greeting[..3];
    let part3 = &greeting[8..];
    let part2 = &greeting[4..7];
    let whole = &greeting[..];
    
    println!("{part1}");
    println!("{part3}");
    println!("{part2}");
    println!("{whole}");
}

The diagram below shows a representation of the slices in the snippet above. slices.png

  • From the above snippet, each slice contains a pointer to the byte at the specified start position and the length of the slice.
  • If you don't specify the first index but specify the end index, the slice begins from position 0 up to the end index minus 1.
  • If you specify the start index but not the end index, the slice begins from the start index until the end of the string.
  • If you specify the start and end index, the slice goes from the start index to the end index minus 1.
  • If you don't specify the start and end indexes, then the slice gives you the entire string.

String literals are string slices

  • In Rust, a string literal is also a string slice and the data type is also &str - just as it is for string slices created from the String data type as shown above.
  • String literals are immutable
  • String literals are hardcoded directly into the final executable/binary of your program. This is because a string literal is known at compile time. For more info, see memory allocation

Consider the below,

fn main() {
    let greeting = "hello world";
}
  • The type of the variable greeting above is also &str. That is, it is a slice pointing to specific point of the binary/executable. See string-literals for more info. This is why string literals are immutable.

String slices as a function parameter

  • Given a function definition which has a string slice parameter i.e., &str, you can pass it any of the following:
    • A string literal since this is of the same type as a string slice i.e., &str
    • A slice of a string literal - this type will also be &str
    • A reference to a String - this makes sense since this is just like passing the full slice of that String
    • A slice of a String - this type will also be &str

Example:

  fn main() {
    let name = "Jack Jones";
  
    print_name(name); // full string literal 
    print_name(&name[0..4]); // slice of string literal
  
    let car = String::from("Mercedes Benz");
 
    print_name(&car); //String reference which is equivalent to a slice of the whole Strong which is &car[..]
    print_name(&car[..]);// full slice of the String
    print_name(&car[9..]); // slice of the String
  }
  
  fn print_name(word: &str) {
    println!("{word}")
  }

Slices of collections

  • You can also use slices with arrays and collections
  fn main() {
    let fruits  = ["apple", "pear", "orange", "mango", "pineapple"];
    
    println!("{:?}", &fruits[1..3]);
  }

In the above, we specify &fruits[1..3] which means we want elements from position 1 in the array up to position 2. Thus, similar to string slices we specify a start index and end index and the result is elements from start index up to end index minus 1.

Slices of collections can be mutable

Unlike a String slice which is immutable, a slice of an array can be mutable if you wish. This is because in case of an array you can index into it using the [] and mutate it as you wish. Consider the snippet below:

fn main() {
    let mut games = ["basket", "ball"];
   
    let games_slice1 = &games[..]; //immutable reference  
    println!("{:?}", games_slice1);
    
    let games_slice2 = & mut games[0..1]; //mutable slice
    games_slice2[0] = "foot";
    println!("{:?}", games);
}