Rust Slices

What Are Slices in Rust?

A slice is a dynamically-sized view into a contiguous sequence of elements in a collection. Unlike arrays, slices do not own the data they reference. They are typically created using a reference and a range of indices.

Slices are represented as a type &[T] for immutable slices or &mut [T] for mutable slices, where T is the type of elements in the slice.

Creating Slices

Slices can be created from collections like arrays or vectors using slicing syntax (&collection[start..end]).

Example 1: Creating a Slice

fn main() {
let numbers = [1, 2, 3, 4, 5];
let slice = &numbers[1..4]; // A slice referencing elements 2, 3, and 4
println!("Slice: {:?}", slice);
}

Output:

Slice: [2, 3, 4]

Explanation:

  • numbers[1..4] creates a slice that includes elements at indices 1, 2, and 3 (end index is exclusive).
  • The slice borrows a portion of the array without taking ownership.

Types of Slices

  1. Immutable Slices (&[T]): Allow read-only access to a part of the collection.
  2. Mutable Slices (&mut [T]): Allow modification of a part of the collection.

Example 2: Mutable Slice

fn main() {
let mut numbers = [1, 2, 3, 4, 5];
let slice = &mut numbers[1..4]; // Mutable slice
slice[0] = 10; // Modifying the slice
println!("Modified array: {:?}", numbers);
}

Output:

Modified array: [1, 10, 3, 4, 5]

Explanation:

  • The mutable slice &mut numbers[1..4] allows modification of the referenced part of the array.

Slices and Functions

Slices are often used in functions to work with parts of a collection. This is useful when you want to avoid ownership transfer while ensuring efficient memory usage.

Example 3: Using Slices in Functions

fn main() {
let numbers = [10, 20, 30, 40, 50];
print_slice(&numbers[1..4]); // Passing a slice to a function
}

fn print_slice(slice: &[i32]) {
for &item in slice {
println!("Item: {}", item);
}
}

Output:

Item: 20  
Item: 30
Item: 40

Explanation:

  • The function print_slice takes an immutable slice and prints its elements.

String Slices

Slices also work with strings, enabling efficient handling of substrings. A string slice is of type &str.

Example 4: String Slices

fn main() {
let text = String::from("Hello, Rust!");
let slice = &text[0..5]; // A slice referencing the first 5 characters
println!("Slice: {}", slice);
}

Output:

Slice: Hello

Key Points:

  • String slices allow you to work with parts of a string without copying.
  • The slicing range refers to byte indices, not characters.

Default Start and End Indices

In slicing syntax, if the start or end index is omitted, Rust uses default values:

  • Start index defaults to 0.
  • End index defaults to the length of the collection.

Example 5: Default Indices

fn main() {
let numbers = [1, 2, 3, 4, 5];

let slice1 = &numbers[..3]; // From start to index 2
let slice2 = &numbers[2..]; // From index 2 to the end

println!("Slice1: {:?}", slice1);
println!("Slice2: {:?}", slice2);
}

Output:

Slice1: [1, 2, 3]  
Slice2: [3, 4, 5]

Slices vs Ownership

Slices are references and follow Rust’s borrowing rules. They are lightweight and efficient because they avoid copying data. Since slices do not own the data, the original collection must remain valid while the slice exists.

Example 6: Ownership and Slices

fn main() {
let array = [1, 2, 3, 4, 5];
let slice = &array[1..3];
println!("Slice: {:?}", slice);

// array goes out of scope here, but slice is invalid if accessed after this
}

Common Errors with Slices

Out-of-Bounds Slicing: Attempting to slice beyond the bounds of a collection causes a runtime panic.

fn main() {
let numbers = [1, 2, 3];
let slice = &numbers[1..4]; // Error: Out of bounds
}

Dangling Slices: A slice pointing to invalid memory is not allowed in Rust. Compile-time checks ensure safety.

Leave a Comment

BoxofLearn