Skip to content

Rust - Slices

A slice is a view or reference into a contiguous block of memory owned by another data structure. Slices allow you to work with a portion of a collection like an array, a vector, or a String without copying the underlying data. This makes them efficient and powerful.

Key properties of slices:

  • They borrow data; they don’t own it.
  • Their size is determined at runtime.
  • They are passed by reference to functions, promoting efficient, ownership-aware code.

The most common type of slice is the string slice, written as &str. It’s a reference to a part of a String or a string literal.

let collection = [1, 2, 3, 4, 5];
let slice = &collection[start_index..end_index]; // `end_index` is exclusive

The range start..end creates a slice that includes elements from start up to, but not including, end.

fn main() {
let s = String::from("Hello, world!");
// Create slices using range syntax
let hello = &s[0..5]; // or &s[..5]
let world = &s[7..12]; // or &s[7..s.len()-1] if you're careful
let full_slice = &s[..]; // A slice of the entire string
println!("Slice 1: {}", hello);
println!("Slice 2: {}", world);
println!("Full slice: {}", full_slice);
}
Slice 1: Hello
Slice 2: world
Full slice: Hello, world!

Warning: Rust strings are UTF-8 encoded. Some characters take more than one byte. If you try to create a string slice in the middle of a multi-byte character, your program will panic at runtime. Slicing must occur on valid character boundaries.

// This will PANIC!
// The character '👋' is multiple bytes long.
let wave = "👋";
// let slice = &wave[0..1]; // Panics: `byte index 1 is not a char boundary`

To safely work with characters, it’s better to iterate over them using .chars().

Slices work just as well on arrays and vectors. The type of a slice of i32 values is &[i32].

fn process_slice(slice: &[i32]) {
println!("Slice has {} elements", slice.len());
println!("First element: {}", slice[0]);
println!("Full slice content: {:?}", slice);
}
fn main() {
let data = [10, 20, 30, 40, 50];
// Pass a slice of the middle three elements
process_slice(&data[1..4]); // Borrows elements at index 1, 2, and 3
}
Slice has 3 elements
First element: 20
Full slice content: [20, 30, 40]

If you have a mutable collection, you can create a mutable slice using &mut. A mutable slice allows you to change the values of the elements it references.

fn modify_slice(slice: &mut [i32]) {
println!("Original slice: {:?}", slice);
slice[0] = 999; // Modify the first element of the slice
println!("Modified slice: {:?}", slice);
}
fn main() {
let mut data = [10, 20, 30, 40, 50];
println!("Original array before: {:?}", data);
// Pass a mutable slice
modify_slice(&mut data[1..4]);
println!("Original array after: {:?}", data);
}
Original array before: [10, 20, 30, 40, 50]
Original slice: [20, 30, 40]
Modified slice: [999, 30, 40]
Original array after: [10, 999, 30, 40, 50]

Notice how modifying the slice directly changed the original data array. This is the power of borrowing in action.