Web Analytics

Slices

Intermediate ~20 min read

Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection. A slice is a kind of reference, so it does not have ownership. Slices are incredibly useful for working with portions of strings and arrays safely.

The Problem: Working with Parts of Data

Let's say we want to write a function that takes a string and returns the first word. Without slices, we might return the index of the end of the word:

fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();
    
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }
    
    s.len()
}
Problem: The index returned has no connection to the String. If the String changes, the index becomes meaningless!

String Slices

A string slice is a reference to part of a String:

let s = String::from("hello world");

let hello = &s[0..5];   // "hello"
let world = &s[6..11];  // "world"

The syntax &s[start..end] creates a slice starting at start and ending just before end (exclusive).

String Slice Memory Layout

Slice Syntax Shortcuts

let s = String::from("hello");

let slice = &s[0..2];  // "he"
let slice = &s[..2];   // Same - start from beginning

let len = s.len();
let slice = &s[3..len];  // "lo"
let slice = &s[3..];     // Same - go to end

let slice = &s[0..len];  // "hello"
let slice = &s[..];      // Same - entire string
String Slice Type: The type of a string slice is written as &str.
Output
Click Run to execute your code

String Literals are Slices

Remember string literals from earlier? They're actually slices!

let s = "Hello, world!";  // Type is &str

The type of s here is &str: it's a slice pointing to that specific point in the binary. This is why string literals are immutable - &str is an immutable reference.

A Better first_word Function

Now we can write first_word to return a slice:

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    
    &s[..]
}

fn main() {
    let my_string = String::from("hello world");
    let word = first_word(&my_string);  // Works with String
    
    let my_literal = "hello world";
    let word = first_word(my_literal);  // Works with &str
}
API Design Tip: Using &str as a parameter type instead of &String makes your API more flexible - it works with both String and &str!

Slices Enforce Safety

Slices prevent bugs at compile time:

Output
Click Run to execute your code
Compile-Time Safety: If you have an immutable reference (slice) to something, you can't also take a mutable reference. The compiler prevents this!

Array Slices

Slices work with arrays too:

let a = [1, 2, 3, 4, 5];

let slice = &a[1..3];  // Type is &[i32]

println!("{:?}", slice);  // [2, 3]

This slice has the type &[i32]. It works the same way as string slices: storing a reference to the first element and a length.

Output
Click Run to execute your code

Slice Types Summary

Collection Type Slice Type Example
String &str &s[0..5]
[i32; 5] &[i32] &arr[1..3]
Vec<i32> &[i32] &vec[2..4]
[char; 10] &[char] &chars[..]

Common Slice Patterns

1. First N Elements

let data = [1, 2, 3, 4, 5];
let first_three = &data[..3];  // [1, 2, 3]

2. Last N Elements

let data = [1, 2, 3, 4, 5];
let len = data.len();
let last_two = &data[len-2..];  // [4, 5]

3. Middle Elements

let data = [1, 2, 3, 4, 5];
let middle = &data[1..4];  // [2, 3, 4]

4. Entire Collection

let data = [1, 2, 3, 4, 5];
let all = &data[..];  // [1, 2, 3, 4, 5]

Slice Best Practices

  • Use &str instead of &String: More flexible API
  • Use slices for function parameters: Works with owned and borrowed data
  • Prefer slices over indices: Safer and more expressive
  • Use range syntax shortcuts: [..n], [n..], [..]
  • Remember slices are references: They don't own data
  • Slices prevent data races: Follow borrowing rules

Exercise: Slices Practice

Task: Complete the slice functions.

Requirements:

  • Return string slices instead of indices
  • Extract specific words from strings
  • Work with array slices
  • Use proper slice syntax
Output
Click Run to execute your code
Show Solution
fn main() {
    let s = String::from("hello world");
    let word = first_word(&s);
    println!("First word: {}", word);
    
    let text = "Rust programming language";
    let second = second_word(text);
    println!("Second word: {}", second);
    
    let data = "abcdefghij";
    let middle = get_middle(data);
    println!("Middle: {}", middle);
    
    let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let evens = get_even_indices(&numbers);
    println!("Even indices: {:?}", evens);
    
    let sentence = "The quick brown fox";
    let last = last_word(sentence);
    println!("Last word: {}", last);
}

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

fn second_word(s: &str) -> &str {
    let words: Vec<&str> = s.split_whitespace().collect();
    if words.len() >= 2 {
        words[1]
    } else {
        ""
    }
}

fn get_middle(s: &str) -> &str {
    let len = s.len();
    if len > 4 {
        &s[2..len-2]
    } else {
        s
    }
}

fn get_even_indices(arr: &[i32]) -> Vec {
    arr.iter()
        .enumerate()
        .filter(|(i, _)| i % 2 == 0)
        .map(|(_, &val)| val)
        .collect()
}

fn last_word(s: &str) -> &str {
    s.split_whitespace().last().unwrap_or("")
}

Summary

  • Slices are references to a contiguous sequence of elements
  • String slices (&str): Reference to part of a String
  • Array slices (&[T]): Reference to part of an array
  • Syntax: &collection[start..end] (end is exclusive)
  • Shortcuts: [..n], [n..], [..]
  • String literals are &str slices
  • Slices don't own data - they're references
  • Slices enforce safety at compile time
  • Use &str for parameters - more flexible than &String
  • Slices follow borrowing rules
  • Slices are zero-cost - just a pointer and length

What's Next?

Congratulations! You've completed the Ownership module - the heart of Rust! You now understand ownership, borrowing, and slices. These concepts make Rust unique and enable memory safety without a garbage collector.

In the next module, we'll explore Structs - how to create custom data types to organize related data together. You'll learn how ownership works with structs and how to build more complex programs.