Slices
Slices in Rust
Slices are references to a contiguous sequence of elements in a collection. They allow you to reference part of a collection without taking ownership.
String Slices
String slices (&str
) are references to part of a String
:
fn main() {
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
println!("First word: {}", hello);
println!("Second word: {}", world);
}
Slice Syntax
fn main() {
let s = String::from("hello");
let slice = &s[0..2]; // "he"
let slice = &s[..2]; // Same as above
let slice = &s[3..s.len()]; // "lo"
let slice = &s[3..]; // Same as above
let slice = &s[0..s.len()]; // "hello"
let slice = &s[..]; // Same as above
}
Array Slices
Slices work with arrays and vectors too:
fn main() {
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // [2, 3]
println!("Slice: {:?}", slice);
// Works with vectors
let v = vec![10, 20, 30, 40, 50];
let v_slice = &v[2..4]; // [30, 40]
println!("Vector slice: {:?}", v_slice);
}
Function Parameters
Slices as parameters make functions more flexible:
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");
// Works with String slices
let word = first_word(&my_string[..]);
println!("First word: {}", word);
// Also works with string literals
let literal = "hello world";
let word = first_word(literal);
println!("First word: {}", word);
}
Mutable Slices
You can create mutable slices:
fn main() {
let mut a = [1, 2, 3, 4, 5];
let slice = &mut a[1..4]; // Mutable slice
slice[0] = 10; // Modify through slice
println!("Modified array: {:?}", a); // [1, 10, 3, 4, 5]
}
Working with Slices
Iterating Over Slices
fn main() {
let a = [10, 20, 30, 40, 50];
let slice = &a[1..4];
for &item in slice {
println!("Item: {}", item);
}
}
Slice Methods
fn main() {
let numbers = [1, 2, 3, 4, 5];
let slice = &numbers[..];
println!("Length: {}", slice.len());
println!("Is empty: {}", slice.is_empty());
if let Some(&first) = slice.first() {
println!("First: {}", first);
}
if let Some(&last) = slice.last() {
println!("Last: {}", last);
}
}
Pattern Matching with Slices
fn main() {
let numbers = [1, 2, 3, 4, 5];
match &numbers[..] {
[] => println!("Empty"),
[single] => println!("Single element: {}", single),
[first, second] => println!("Two elements: {}, {}", first, second),
[first, .., last] => println!("First: {}, Last: {}", first, last),
}
}
String Literals Are Slices
String literals are actually slices:
fn main() {
let s: &str = "Hello, world!"; // &str is a slice
println!("String literal: {}", s);
// This is why this works
fn takes_slice(s: &str) {
println!("{}", s);
}
takes_slice("literal"); // String literal
takes_slice(&String::from("String")); // Deref coercion
}
Common Slice Operations
Splitting Strings
fn main() {
let text = "hello,world,rust";
for word in text.split(',') {
println!("Word: {}", word);
}
let parts: Vec<&str> = text.split(',').collect();
println!("Parts: {:?}", parts);
}
Finding Substrings
fn main() {
let s = "Hello, Rust!";
if s.contains("Rust") {
println!("Found Rust!");
}
if let Some(index) = s.find("Rust") {
println!("Rust found at index: {}", index);
let rust_slice = &s[index..];
println!("Slice from Rust: {}", rust_slice);
}
}
Chunking Slices
fn main() {
let data = [1, 2, 3, 4, 5, 6, 7, 8];
for chunk in data.chunks(3) {
println!("Chunk: {:?}", chunk);
}
}
Advanced Slice Patterns
Windows
fn main() {
let numbers = [1, 2, 3, 4, 5];
for window in numbers.windows(3) {
println!("Window: {:?}", window);
}
}
Split at Index
fn main() {
let numbers = [1, 2, 3, 4, 5];
let (left, right) = numbers.split_at(3);
println!("Left: {:?}", left); // [1, 2, 3]
println!("Right: {:?}", right); // [4, 5]
}
Binary Search on Sorted Slices
fn main() {
let sorted = [1, 3, 5, 7, 9, 11];
match sorted.binary_search(&5) {
Ok(index) => println!("Found 5 at index: {}", index),
Err(index) => println!("5 should be inserted at index: {}", index),
}
}
Practical Examples
Word Counter
fn count_words(text: &str) -> usize {
text.split_whitespace().count()
}
fn main() {
let text = "Hello world from Rust";
println!("Word count: {}", count_words(text));
}
Custom Split Function
fn split_at_char(s: &str, ch: char) -> (&str, &str) {
if let Some(index) = s.find(ch) {
(&s[..index], &s[index + 1..])
} else {
(s, "")
}
}
fn main() {
let email = "[email protected]";
let (username, domain) = split_at_char(email, '@');
println!("Username: {}", username);
println!("Domain: {}", domain);
}
Processing Data in Chunks
fn process_batch(data: &[i32]) -> i32 {
data.iter().sum()
}
fn main() {
let large_data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let batch_size = 3;
for (i, batch) in large_data.chunks(batch_size).enumerate() {
let sum = process_batch(batch);
println!("Batch {}: sum = {}", i, sum);
}
}
Best Practices
- Use
&str
parameters instead of&String
for more flexibility - Return slices carefully - ensure the data outlives the slice
- Prefer slices over indexing for safety and clarity
- Use iterator methods on slices for functional programming style
- Be careful with UTF-8 - string slicing can panic if not on char boundaries
Common Pitfalls
Invalid UTF-8 Boundaries
fn main() {
let hello = "Здравствуйте"; // Russian "Hello"
// This will panic - not on char boundary
// let s = &hello[0..1];
// Use char indices instead
let s = &hello[0..2]; // First character (2 bytes)
println!("First char: {}", s);
}
Lifetime Issues
// This won't compile
// fn bad_slice() -> &[i32] {
// let v = vec![1, 2, 3];
// &v[..] // Error: v is dropped
// }
// Return owned data instead
fn good_slice() -> Vec<i32> {
vec![1, 2, 3]
}