Rust - Slices
Overview
Estimated time: 40–50 minutes
Master Rust slices, which allow you to reference a contiguous sequence of elements in a collection without taking ownership. Learn string slices, array slices, slice methods, and how slices work with Rust's ownership system.
Learning Objectives
- Understand what slices are and how they work with ownership.
- Master string slices (
&str
) and their relationship toString
. - Learn array and vector slices (
&[T]
). - Understand slice methods and iteration patterns.
- Learn when and how to use slices effectively.
Prerequisites
- Rust - Introduction
- Rust - Variables & Mutability
- Rust - Data Types
- Rust - Ownership Basics
- Rust - References & Borrowing
What Are Slices?
A slice is a reference to a contiguous sequence of elements in a collection. Slices are borrowing references that don't own the data they point to.
Slice Fundamentals
fn main() {
let numbers = [1, 2, 3, 4, 5, 6, 7, 8];
// Full slice of the array
let full_slice: &[i32] = &numbers;
println!("Full slice: {:?}", full_slice);
// Partial slices using range syntax
let first_three = &numbers[0..3]; // Elements 0, 1, 2
let middle = &numbers[2..6]; // Elements 2, 3, 4, 5
let last_two = &numbers[6..8]; // Elements 6, 7
println!("First three: {:?}", first_three);
println!("Middle: {:?}", middle);
println!("Last two: {:?}", last_two);
// Slice shortcuts
let from_start = &numbers[..4]; // Same as &numbers[0..4]
let to_end = &numbers[3..]; // From index 3 to end
let entire = &numbers[..]; // Entire array
println!("From start: {:?}", from_start);
println!("To end: {:?}", to_end);
println!("Entire: {:?}", entire);
// Slice properties
println!("Slice length: {}", middle.len());
println!("Is empty: {}", middle.is_empty());
}
Expected Output:
Full slice: [1, 2, 3, 4, 5, 6, 7, 8]
First three: [1, 2, 3]
Middle: [3, 4, 5, 6]
Last two: [7, 8]
From start: [1, 2, 3, 4]
To end: [4, 5, 6, 7, 8]
Entire: [1, 2, 3, 4, 5, 6, 7, 8]
Slice length: 4
Is empty: false
String Slices (&str)
String slices are the most common type of slice in Rust. String literals are string slices.
String Slice Basics
fn main() {
let text = String::from("Hello, beautiful world!");
// String slices from String
let hello = &text[0..5]; // "Hello"
let beautiful = &text[7..16]; // "beautiful"
let world = &text[17..22]; // "world"
println!("Original: {}", text);
println!("hello: {}", hello);
println!("beautiful: {}", beautiful);
println!("world: {}", world);
// String literals are already string slices
let literal: &str = "This is a string slice";
println!("Literal: {}", literal);
// Slicing with different ranges
let greeting = &text[..5]; // From start to index 5
let ending = &text[17..]; // From index 17 to end
let full = &text[..]; // Entire string
println!("Greeting: {}", greeting);
println!("Ending: {}", ending);
println!("Full: {}", full);
// Functions that work with string slices
fn print_slice(s: &str) {
println!("Slice: '{}'", s);
}
print_slice(&text); // Pass String as slice
print_slice(literal); // Pass string literal
print_slice(&text[7..16]); // Pass substring slice
}
Expected Output:
Original: Hello, beautiful world!
hello: Hello
beautiful: beautiful
world: world
Literal: This is a string slice
Greeting: Hello
Ending: world!
Full: Hello, beautiful world!
Slice: 'Hello, beautiful world!'
Slice: 'This is a string slice'
Slice: 'beautiful'
Safe String Slicing
fn main() {
let text = "Hello, 世界! 🦀";
println!("Text: {}", text);
println!("Byte length: {}", text.len());
println!("Character count: {}", text.chars().count());
// Unsafe: Direct byte indexing can panic with Unicode
// let bad_slice = &text[0..8]; // Might panic if it cuts through a Unicode character
// Safe: Use char boundaries
let char_indices: Vec<_> = text.char_indices().collect();
println!("Character boundaries: {:?}", char_indices);
// Safe slicing using char_indices
if let Some((start, _)) = char_indices.get(0) {
if let Some((end, _)) = char_indices.get(7) {
let safe_slice = &text[*start..*end];
println!("Safe slice: {}", safe_slice);
}
}
// Using get() for safe slicing
match text.get(0..5) {
Some(slice) => println!("Got slice: {}", slice),
None => println!("Invalid slice range"),
}
// Working with characters directly
let chars: Vec = text.chars().collect();
let char_slice = &chars[0..5];
let back_to_string: String = char_slice.iter().collect();
println!("Character slice: {:?}", char_slice);
println!("Back to string: {}", back_to_string);
}
Expected Output:
Text: Hello, 世界! 🦀
Byte length: 17
Character count: 10
Character boundaries: [(0, 'H'), (1, 'e'), (2, 'l'), (3, 'l'), (4, 'o'), (5, ','), (6, ' '), (7, '世'), (10, '界'), (13, '!'), (14, ' '), (15, '🦀')]
Safe slice: Hello, 世
Got slice: Hello
Character slice: ['H', 'e', 'l', 'l', 'o']
Back to string: Hello
Array and Vector Slices
Working with Array Slices
fn main() {
let numbers = [10, 20, 30, 40, 50, 60, 70, 80];
// Different slice types
let slice: &[i32] = &numbers[2..6];
println!("Slice: {:?}", slice);
// Slices maintain type information
let first_half = &numbers[..4];
let second_half = &numbers[4..];
println!("First half: {:?}", first_half);
println!("Second half: {:?}", second_half);
// Functions that work with slices
fn sum_slice(slice: &[i32]) -> i32 {
slice.iter().sum()
}
fn find_max(slice: &[i32]) -> Option<&i32> {
slice.iter().max()
}
println!("Sum of slice: {}", sum_slice(slice));
println!("Max in first half: {:?}", find_max(first_half));
// Working with different array types
let floats = [1.1, 2.2, 3.3, 4.4, 5.5];
let float_slice: &[f64] = &floats[1..4];
println!("Float slice: {:?}", float_slice);
// Slices of different sizes from same array
let small = &numbers[0..2];
let medium = &numbers[0..4];
let large = &numbers[0..6];
println!("Small: {:?} (len: {})", small, small.len());
println!("Medium: {:?} (len: {})", medium, medium.len());
println!("Large: {:?} (len: {})", large, large.len());
}
Expected Output:
Slice: [30, 40, 50, 60]
First half: [10, 20, 30, 40]
Second half: [50, 60, 70, 80]
Sum of slice: 180
Max in first half: Some(40)
Float slice: [2.2, 3.3, 4.4]
Small: [10, 20] (len: 2)
Medium: [10, 20, 30, 40] (len: 4)
Large: [10, 20, 30, 40, 50, 60] (len: 6)
Vector Slices
fn main() {
let mut vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Immutable slices from vector
let middle = &vec[3..7];
println!("Middle slice: {:?}", middle);
// Mutable slices
let mutable_slice = &mut vec[0..3];
println!("Before modification: {:?}", mutable_slice);
// Modify through mutable slice
for element in mutable_slice.iter_mut() {
*element *= 2;
}
println!("After modification: {:?}", vec);
// Slice methods work on vectors too
let odds: Vec<&i32> = vec.iter().filter(|&&x| x % 2 == 1).collect();
println!("Odd numbers: {:?}", odds);
// Dynamic data with slices
vec.push(11);
vec.push(12);
let last_four = &vec[vec.len()-4..];
println!("Last four elements: {:?}", last_four);
// Converting vector to slice for function calls
fn process_data(data: &[i32]) -> i32 {
data.iter().map(|x| x * x).sum()
}
let result = process_data(&vec); // Convert Vec to slice
println!("Sum of squares: {}", result);
}
Expected Output:
Middle slice: [4, 5, 6, 7]
Before modification: [1, 2, 3]
After modification: [2, 4, 6, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Odd numbers: [5, 7, 9, 11]
Last four elements: [9, 10, 11, 12]
Sum of squares: 650
Slice Methods and Operations
Common Slice Methods
fn main() {
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let slice = &numbers[..];
// Basic properties
println!("Length: {}", slice.len());
println!("Is empty: {}", slice.is_empty());
println!("First element: {:?}", slice.first());
println!("Last element: {:?}", slice.last());
// Safe element access
println!("Element at index 3: {:?}", slice.get(3));
println!("Element at index 100: {:?}", slice.get(100));
// Splitting slices
let (left, right) = slice.split_at(5);
println!("Split at 5: {:?} | {:?}", left, right);
// Finding elements
println!("Contains 7: {}", slice.contains(&7));
println!("Position of 6: {:?}", slice.iter().position(|&x| x == 6));
// Binary search (requires sorted slice)
match slice.binary_search(&7) {
Ok(index) => println!("Found 7 at index {}", index),
Err(index) => println!("7 would be inserted at index {}", index),
}
// Windows and chunks
println!("Windows of size 3:");
for window in slice.windows(3) {
println!(" {:?}", window);
}
println!("Chunks of size 3:");
for chunk in slice.chunks(3) {
println!(" {:?}", chunk);
}
}
Expected Output:
Length: 10
Is empty: false
First element: Some(1)
Last element: Some(10)
Element at index 3: Some(4)
Element at index 100: None
Split at 5: [1, 2, 3, 4, 5] | [6, 7, 8, 9, 10]
Contains 7: true
Position of 6: Some(5)
Found 7 at index 6
Windows of size 3:
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 10]
Chunks of size 3:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10]
Slice Iteration Patterns
fn main() {
let data = [10, 20, 30, 40, 50];
let slice = &data[..];
// Basic iteration
println!("Basic iteration:");
for item in slice {
println!(" {}", item);
}
// Iteration with indices
println!("With indices:");
for (index, item) in slice.iter().enumerate() {
println!(" {}: {}", index, item);
}
// Iterator methods
let doubled: Vec = slice.iter().map(|x| x * 2).collect();
println!("Doubled: {:?}", doubled);
let sum: i32 = slice.iter().sum();
println!("Sum: {}", sum);
let even_count = slice.iter().filter(|&&x| x % 20 == 0).count();
println!("Count of numbers divisible by 20: {}", even_count);
// Finding elements
let first_big = slice.iter().find(|&&x| x > 25);
println!("First number > 25: {:?}", first_big);
// All/any operations
let all_positive = slice.iter().all(|&&x| x > 0);
let any_even = slice.iter().any(|&&x| x % 2 == 0);
println!("All positive: {}", all_positive);
println!("Any even: {}", any_even);
// Folding (reducing)
let product = slice.iter().fold(1, |acc, &x| acc * x);
println!("Product: {}", product);
// Collecting different types
let as_strings: Vec = slice.iter().map(|x| x.to_string()).collect();
println!("As strings: {:?}", as_strings);
}
Expected Output:
Basic iteration:
10
20
30
40
50
With indices:
0: 10
1: 20
2: 30
3: 40
4: 50
Doubled: [20, 40, 60, 80, 100]
Sum: 150
Count of numbers divisible by 20: 2
First number > 25: Some(30)
All positive: true
Any even: true
Product: 12000000
As strings: ["10", "20", "30", "40", "50"]
Mutable Slices
Modifying Through Mutable Slices
fn main() {
let mut numbers = [5, 2, 8, 1, 9, 3, 7, 4, 6];
println!("Original: {:?}", numbers);
// Mutable slice
let slice = &mut numbers[..];
// Modify elements directly
slice[0] = 100;
slice[8] = 200;
println!("After direct modification: {:?}", numbers);
// Modify through iteration
for element in slice.iter_mut() {
if *element < 10 {
*element *= 10;
}
}
println!("After iteration modification: {:?}", numbers);
// Slice operations that modify
let middle = &mut numbers[2..7];
middle.reverse();
println!("After reversing middle section: {:?}", numbers);
// Sort a portion
let to_sort = &mut numbers[1..6];
to_sort.sort();
println!("After sorting section: {:?}", numbers);
// Swap elements
slice.swap(0, 8);
println!("After swapping first and last: {:?}", numbers);
// Fill with value
let end_section = &mut numbers[6..9];
end_section.fill(999);
println!("After filling end section: {:?}", numbers);
}
Expected Output:
Original: [5, 2, 8, 1, 9, 3, 7, 4, 6]
After direct modification: [100, 2, 8, 1, 9, 3, 7, 4, 200]
After iteration modification: [100, 20, 80, 10, 90, 30, 70, 40, 200]
After reversing middle section: [100, 20, 30, 90, 10, 80, 70, 40, 200]
After sorting section: [100, 10, 20, 30, 80, 90, 70, 40, 200]
After swapping first and last: [200, 10, 20, 30, 80, 90, 70, 40, 100]
After filling end section: [200, 10, 20, 30, 80, 90, 999, 999, 999]
Splitting Mutable Slices
fn main() {
let mut data = [1, 2, 3, 4, 5, 6, 7, 8];
println!("Original: {:?}", data);
// Split mutable slice
let (left, right) = data.split_at_mut(4);
// Modify both parts simultaneously
for item in left.iter_mut() {
*item *= 10;
}
for item in right.iter_mut() {
*item += 100;
}
println!("After split modification: {:?}", data);
// Split multiple ways
let mut matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// Get mutable slices of rows
let (first_two, last_one) = matrix.split_at_mut(2);
// Modify first two rows
for row in first_two.iter_mut() {
for cell in row.iter_mut() {
*cell *= 2;
}
}
// Modify last row
for cell in last_one[0].iter_mut() {
*cell += 10;
}
println!("Modified matrix:");
for row in &matrix {
println!(" {:?}", row);
}
}
Expected Output:
Original: [1, 2, 3, 4, 5, 6, 7, 8]
After split modification: [10, 20, 30, 40, 105, 106, 107, 108]
Modified matrix:
[2, 4, 6]
[8, 10, 12]
[17, 18, 19]
Slice Patterns and Matching
Pattern Matching with Slices
fn main() {
let numbers = [1, 2, 3, 4, 5];
// Function that processes different slice patterns
fn describe_slice(slice: &[i32]) {
match slice {
[] => println!("Empty slice"),
[x] => println!("Single element: {}", x),
[x, y] => println!("Two elements: {} and {}", x, y),
[x, y, z] => println!("Three elements: {}, {}, {}", x, y, z),
[first, .., last] => println!("Multiple elements: first = {}, last = {}", first, last),
}
}
// Test different slice sizes
describe_slice(&[]);
describe_slice(&[42]);
describe_slice(&[1, 2]);
describe_slice(&[1, 2, 3]);
describe_slice(&numbers);
describe_slice(&numbers[1..4]);
// More complex pattern matching
fn analyze_sequence(slice: &[i32]) -> String {
match slice {
[] => "Empty".to_string(),
[x] if *x > 0 => format!("Single positive: {}", x),
[x] if *x < 0 => format!("Single negative: {}", x),
[x] => format!("Single zero: {}", x),
[x, y] if x < y => format!("Ascending pair: {} < {}", x, y),
[x, y] if x > y => format!("Descending pair: {} > {}", x, y),
[x, y] => format!("Equal pair: {} = {}", x, y),
[first, middle @ .., last] if first < last => {
format!("Ascending sequence: {} .. {} (middle: {} elements)",
first, last, middle.len())
},
[first, middle @ .., last] => {
format!("Non-ascending sequence: {} .. {} (middle: {} elements)",
first, last, middle.len())
},
}
}
println!("\nSequence analysis:");
let test_cases = [
&[][..], // Empty
&[42][..], // Single
&[1, 5][..], // Ascending pair
&[5, 1][..], // Descending pair
&[3, 3][..], // Equal pair
&[1, 2, 3, 4, 5][..], // Ascending sequence
&[5, 3, 1, 2, 0][..], // Non-ascending sequence
];
for case in &test_cases {
println!(" {:?} -> {}", case, analyze_sequence(case));
}
}
Expected Output:
Empty slice
Single element: 42
Two elements: 1 and 2
Three elements: 1, 2, 3
Multiple elements: first = 1, last = 5
Multiple elements: first = 2, last = 4
Sequence analysis:
[] -> Empty
[42] -> Single positive: 42
[1, 5] -> Ascending pair: 1 < 5
[5, 1] -> Descending pair: 5 > 1
[3, 3] -> Equal pair: 3 = 3
[1, 2, 3, 4, 5] -> Ascending sequence: 1 .. 5 (middle: 3 elements)
[5, 3, 1, 2, 0] -> Non-ascending sequence: 5 .. 0 (middle: 3 elements)
Practical Examples
Text Processing with String Slices
fn main() {
let text = "The quick brown fox jumps over the lazy dog";
// Word extraction and analysis
fn analyze_text(text: &str) {
let words: Vec<&str> = text.split_whitespace().collect();
println!("Text: {}", text);
println!("Word count: {}", words.len());
// Find longest and shortest words
let longest = words.iter().max_by_key(|word| word.len()).unwrap();
let shortest = words.iter().min_by_key(|word| word.len()).unwrap();
println!("Longest word: '{}' ({} chars)", longest, longest.len());
println!("Shortest word: '{}' ({} chars)", shortest, shortest.len());
// Words starting with specific letter
let t_words: Vec<&&str> = words.iter().filter(|word| word.starts_with('t')).collect();
println!("Words starting with 't': {:?}", t_words);
// Create acronym
let acronym: String = words.iter()
.map(|word| word.chars().next().unwrap().to_uppercase().to_string())
.collect();
println!("Acronym: {}", acronym);
}
analyze_text(text);
// Extract file extension
fn get_extension(filename: &str) -> Option<&str> {
filename.rfind('.').map(|i| &filename[i+1..])
}
let filenames = ["document.pdf", "image.png", "script.js", "readme"];
println!("\nFile extensions:");
for filename in &filenames {
match get_extension(filename) {
Some(ext) => println!(" {} -> {}", filename, ext),
None => println!(" {} -> no extension", filename),
}
}
}
Expected Output:
Text: The quick brown fox jumps over the lazy dog
Word count: 9
Longest word: 'quick' (5 chars)
Shortest word: 'The' (3 chars)
Words starting with 't': ["the"]
Acronym: TQBFJOTLD
File extensions:
document.pdf -> pdf
image.png -> png
script.js -> js
readme -> no extension
Data Processing with Array Slices
fn main() {
let data = [
85, 92, 78, 96, 88, 94, 82, 90, 87, 91,
76, 89, 95, 83, 97, 79, 86, 93, 84, 81
];
// Statistical analysis using slices
fn analyze_data(slice: &[i32]) -> (f64, i32, i32, f64) {
let sum: i32 = slice.iter().sum();
let mean = sum as f64 / slice.len() as f64;
let min = *slice.iter().min().unwrap();
let max = *slice.iter().max().unwrap();
// Standard deviation
let variance = slice.iter()
.map(|&x| (x as f64 - mean).powi(2))
.sum::() / slice.len() as f64;
let std_dev = variance.sqrt();
(mean, min, max, std_dev)
}
println!("Full dataset analysis:");
let (mean, min, max, std_dev) = analyze_data(&data);
println!(" Mean: {:.2}", mean);
println!(" Min: {}, Max: {}", min, max);
println!(" Standard deviation: {:.2}", std_dev);
// Analyze different sections
let sections = [
("First half", &data[..10]),
("Second half", &data[10..]),
("Middle section", &data[5..15]),
("High performers", &data[..]), // We'll filter this
];
for (name, section) in §ions[..3] { // Skip the last one for now
println!("\n{} analysis:", name);
let (mean, min, max, std_dev) = analyze_data(section);
println!(" Mean: {:.2}, Range: {}-{}, Std Dev: {:.2}",
mean, min, max, std_dev);
}
// Filter high performers (above average + 1 std dev)
let overall_mean = mean;
let threshold = overall_mean + std_dev;
let high_performers: Vec = data.iter()
.filter(|&&score| score as f64 > threshold)
.cloned()
.collect();
println!("\nHigh performers (score > {:.2}):", threshold);
println!(" Scores: {:?}", high_performers);
println!(" Count: {}", high_performers.len());
}
Expected Output:
Full dataset analysis:
Mean: 87.35
Min: 76, Max: 97
Standard deviation: 5.58
First half analysis:
Mean: 87.30, Range: 78-96, Std Dev: 5.64
Second half analysis:
Mean: 87.40, Range: 76-97, Std Dev: 5.66
Middle section analysis:
Mean: 87.10, Range: 79-95, Std Dev: 5.63
High performers (score > 92.93):
Scores: [96, 94, 95, 97, 93]
Count: 5
Best Practices
Function Design with Slices
fn main() {
// Good: Accept slices for maximum flexibility
fn process_numbers(numbers: &[i32]) -> i32 {
numbers.iter().map(|x| x * x).sum()
}
// This function works with arrays, vectors, and slices
let array = [1, 2, 3, 4];
let vector = vec![1, 2, 3, 4];
let slice = &vector[1..3];
println!("Array result: {}", process_numbers(&array));
println!("Vector result: {}", process_numbers(&vector));
println!("Slice result: {}", process_numbers(slice));
// Good: Return slices when possible
fn get_words(text: &str) -> Vec<&str> {
text.split_whitespace().collect()
}
let text = "hello world rust programming";
let words = get_words(text);
println!("Words: {:?}", words);
// Efficient: Avoid unnecessary allocations
fn find_word_starting_with(text: &str, prefix: &str) -> Option<&str> {
text.split_whitespace()
.find(|word| word.starts_with(prefix))
}
if let Some(word) = find_word_starting_with(text, "rust") {
println!("Found word starting with 'rust': {}", word);
}
}
Expected Output:
Array result: 30
Vector result: 30
Slice result: 13
Words: ["hello", "world", "rust", "programming"]
Found word starting with 'rust': rust
Common Pitfalls and Solutions
- Unicode slicing: Use
char_indices()
for safe string slicing - Bounds checking: Use
get()
instead of direct indexing for safety - Unnecessary allocations: Prefer slices over owned collections in function parameters
- Lifetime issues: Remember that slices borrow data, ensure the source lives long enough
Checks for Understanding
- What's the difference between a slice and an array?
- Why might
&string[0..5]
panic with Unicode text? - What does
&mut [T]
allow you to do that&[T]
doesn't? - How do you safely get a substring without risking a panic?
- What's the benefit of accepting
&[T]
instead ofVec
in function parameters?
Answers
- A slice is a reference to a contiguous sequence; an array is owned data with fixed size known at compile time
- Because Unicode characters can be multiple bytes, and slicing by byte index might cut through a character
&mut [T]
allows mutation of elements;&[T]
is read-only- Use
get()
method or ensure slice bounds are on character boundaries withchar_indices()
- More flexible - works with arrays, vectors, and other slices; doesn't transfer ownership