Rust by Example: Slices and Views
Understand slices, a key primitive for efficient data access. This example shows how to create references to contiguous sequences of elements in a collection, such as string slices (&str) and array slices, avoiding unnecessary data copying.
Code
fn main() {
// --- String Slices ---
let s = String::from("Hello world");
// A slice is a reference to a part of a String
let hello = &s[0..5];
let world = &s[6..11];
println!("Slice 1: {}", hello);
println!("Slice 2: {}", world);
// Range syntax shortcuts
let len = s.len();
let slice1 = &s[0..2]; // "He"
let slice2 = &s[..2]; // "He" (same as above)
let slice3 = &s[3..len]; // "lo world"
let slice4 = &s[3..]; // "lo world" (same as above)
let slice5 = &s[..]; // "Hello world" (entire string)
println!("Full slice: {}", slice5);
// --- Array Slices ---
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // [2, 3]
println!("Array slice: {:?}", slice);
// Slices as function parameters
// This allows the function to accept both String and &str
let word = first_word(&s);
println!("First word: {}", word);
}
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[..]
}Explanation
A Slice is a data type that does not have ownership. Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection. This is incredibly useful for writing efficient code because it avoids copying data. For example, if you want to process just the first word of a sentence, you don't need to create a new string containing that word; you can just create a slice that points to the existing bytes in memory.
Internally, a slice is represented as a "Fat Pointer". Unlike a regular pointer which is just a memory address, a fat pointer contains two words:
- Pointer: The memory address of the start of the slice.
- Length: The number of elements in the slice.
String literals in Rust are actually slices. When you write let s = "Hello";, the variable s is of type &str. It's a slice pointing to that specific point in the binary's read-only memory where the string is stored. This is why string literals are immutable.
Code Breakdown
let hello = &s[0..5];. We are creating a slice. Internally, this slice stores two things: a pointer to the byte at index 0 of s, and a length of 5. No data is copied.fn first_word(s: &str). By accepting &str instead of &String, this function becomes much more flexible. It can accept references to heap-allocated Strings and string literals.bytes.iter().enumerate(). This is a common pattern for iterating over a collection when you need both the index and the value. enumerate wraps the iterator and returns a tuple (index, reference_to_value).
