Slices
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()
}
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).
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
&str.
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
}
&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:
Click Run to execute your code
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.
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
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
&strslices - Slices don't own data - they're references
- Slices enforce safety at compile time
- Use
&strfor 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.
Enjoying these tutorials?