Bird
Raised Fist0
Rustprogramming~15 mins

Array slices in Rust - Deep Dive

Choose your learning style10 modes available
Overview - Array slices
What is it?
An array slice in Rust is a way to view a part or all of an array without copying it. It lets you work with a continuous section of an array by referencing it, not owning it. Slices have a fixed size known at runtime and provide safe access to elements. They are like windows into arrays that let you read or modify elements safely.
Why it matters
Without slices, you would have to copy parts of arrays to work on them, which wastes memory and time. Slices let you share parts of arrays efficiently and safely, avoiding bugs like accessing invalid memory. They make programs faster and more reliable by preventing common mistakes with array handling.
Where it fits
Before learning slices, you should understand basic arrays and references in Rust. After slices, you can learn about vectors, string slices, and more advanced borrowing concepts. Slices are a foundation for safe and efficient data handling in Rust.
Mental Model
Core Idea
An array slice is a safe, read-only or mutable view into a continuous part of an array without copying it.
Think of it like...
Imagine a long book (the array) and a bookmark that marks a few pages (the slice). The bookmark doesn’t move or copy the pages; it just points to a part of the book you want to read or write notes on.
Array: [A][B][C][D][E][F][G][H]
Slice:       ┌─────────────┐
             │C][D][E][F] │
             └─────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding Rust arrays
🤔
Concept: Learn what arrays are and how they store data in Rust.
In Rust, an array is a fixed-size collection of elements of the same type stored contiguously in memory. For example, let arr = [10, 20, 30, 40, 50]; creates an array of 5 integers. Arrays have a fixed size known at compile time.
Result
You can access elements by index, like arr[2] gives 30.
Knowing arrays are fixed-size and stored continuously helps understand why slices can safely reference parts of them.
2
FoundationReferences and borrowing basics
🤔
Concept: Learn how Rust references let you access data without taking ownership.
Rust uses references (&) to let you look at data without owning it. For example, let r = &arr; means r points to the whole array. Borrowing rules ensure safety by preventing data races and invalid access.
Result
You can read or modify data through references following Rust's rules.
Understanding references is key because slices are special kinds of references to parts of arrays.
3
IntermediateCreating and using array slices
🤔Before reading on: do you think slices copy data or just reference it? Commit to your answer.
Concept: Learn how to create slices that reference parts of arrays without copying.
You create a slice by specifying a range on an array reference: let slice = &arr[1..4]; This slice points to elements at indexes 1, 2, and 3. You can read elements from the slice like slice[0] which is arr[1].
Result
slice contains [20, 30, 40] referencing arr without copying.
Knowing slices don’t copy data helps write efficient code that avoids unnecessary memory use.
4
IntermediateMutable slices for safe modification
🤔Before reading on: can you modify array elements through a slice? Commit to your answer.
Concept: Learn how mutable slices let you change parts of an array safely.
You can create a mutable slice with let slice = &mut arr[2..5]; This lets you change elements in that range. For example, slice[0] = 100; changes arr[2] to 100. Rust ensures no other references can modify the array at the same time.
Result
The original array changes where the slice points, safely and without copying.
Understanding mutable slices enforces Rust’s safety guarantees while allowing flexible data changes.
5
IntermediateSlice length and bounds checking
🤔
Concept: Learn how slices know their length and prevent out-of-bounds access.
Slices carry their length at runtime, so accessing slice[10] when length is 3 causes a panic. Rust checks bounds on every access to prevent invalid memory reads or writes. You can get the length with slice.len().
Result
Safe access to slice elements with runtime checks prevents crashes or bugs.
Knowing slices track length and check bounds explains why Rust programs avoid common memory errors.
6
AdvancedSlices and function parameters
🤔Before reading on: do you think functions can accept slices to work on parts of arrays? Commit to your answer.
Concept: Learn how slices let functions work with parts of arrays without copying or ownership transfer.
Functions can take parameters like fn process(slice: &[i32]) to accept any slice of integers. This lets you write flexible code that works on whole arrays or parts. For mutable changes, use &mut [i32].
Result
Functions can safely read or modify array parts without copying or ownership issues.
Understanding slices as function parameters unlocks powerful, reusable code patterns.
7
ExpertSlice internals and fat pointers
🤔Before reading on: do you know how Rust represents slices internally? Commit to your answer.
Concept: Learn that slices are fat pointers containing a pointer and a length, enabling safe access.
Internally, a slice stores two pieces: a pointer to the first element and the length of the slice. This 'fat pointer' lets Rust know where the slice starts and how many elements it covers. This design enables runtime bounds checking and safe iteration.
Result
Slices provide safety and flexibility by combining pointer and length in one value.
Knowing the fat pointer structure explains how Rust enforces safety without runtime overhead.
Under the Hood
Rust slices are implemented as fat pointers, which hold a memory address and a length. When you create a slice, Rust does not copy data but creates this fat pointer referencing the original array's memory. Every access through the slice checks the index against the stored length to prevent out-of-bounds errors. Mutable slices require exclusive access, enforced by Rust's borrow checker, preventing data races and ensuring memory safety.
Why designed this way?
Rust was designed for safety and performance. Using fat pointers for slices allows zero-cost abstraction: no copying or extra runtime overhead beyond bounds checks. This design balances flexibility with strict safety guarantees. Alternatives like copying data would waste resources, and unsafe raw pointers would risk memory bugs. The fat pointer approach is a modern solution to safe, efficient data access.
┌───────────────┐
│   Array Data  │
│ [10][20][30][40][50] │
└─────┬─────────┘
      │
      ▼
┌───────────────────────────┐
│ Slice (fat pointer)        │
│ ┌───────────────┐         │
│ │ Pointer to 20 │─────────┤──▶ Points inside array
│ └───────────────┘         │
│ ┌───────────────┐         │
│ │ Length = 3    │         │
│ └───────────────┘         │
└───────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does creating a slice copy the array data? Commit to yes or no.
Common Belief:Creating a slice copies the part of the array it references.
Tap to reveal reality
Reality:Slices do not copy data; they only create a reference to the existing array elements.
Why it matters:Believing slices copy data leads to inefficient code and misunderstanding of memory usage.
Quick: Can you have multiple mutable slices to the same array at once? Commit to yes or no.
Common Belief:You can have many mutable slices to the same array simultaneously.
Tap to reveal reality
Reality:Rust forbids multiple mutable slices to the same data at the same time to prevent data races.
Why it matters:Ignoring this causes compilation errors and unsafe behavior in concurrent code.
Quick: Does a slice know its length at compile time? Commit to yes or no.
Common Belief:Slices have a fixed length known at compile time like arrays.
Tap to reveal reality
Reality:Slices have a length known only at runtime, unlike arrays which have compile-time fixed size.
Why it matters:Confusing this can cause errors when trying to use slices where fixed-size arrays are required.
Quick: Can you create a slice that skips elements (non-contiguous)? Commit to yes or no.
Common Belief:Slices can reference non-contiguous elements of an array.
Tap to reveal reality
Reality:Slices always reference a continuous block of elements; non-contiguous views require other data structures.
Why it matters:Expecting non-contiguous slices leads to bugs and misuse of slice APIs.
Expert Zone
1
Slices are unsized types behind the scenes, which means they cannot exist alone but only behind pointers like & or &mut.
2
When slicing strings, Rust uses UTF-8 byte slices, so slicing must respect character boundaries to avoid invalid data.
3
Slices can be coerced from arrays automatically, but coercion from vectors or other collections requires explicit referencing.
When NOT to use
Avoid slices when you need ownership or dynamic resizing; use Vec instead. Also, for non-contiguous data access, consider iterators or specialized data structures like linked lists or hash maps.
Production Patterns
Slices are widely used in APIs to accept flexible input without copying, such as parsing functions, sorting algorithms, and data processing pipelines. They enable zero-copy views and safe mutation patterns in concurrent and performance-critical Rust code.
Connections
Pointers and references
Slices build on the idea of references by adding length information.
Understanding references helps grasp how slices safely point to data with added size context.
String slices (&str)
String slices are a specialized form of array slices for UTF-8 text data.
Knowing array slices clarifies how string slices work and why they must respect character boundaries.
Memory safety in operating systems
Slices enforce safe memory access similar to how OS memory protection prevents invalid reads.
Recognizing slices as a software-level memory safety tool connects programming safety to system-level protections.
Common Pitfalls
#1Trying to create a slice with an out-of-bounds range.
Wrong approach:let slice = &arr[3..10]; // arr has length 5, this panics
Correct approach:let slice = &arr[3..5]; // valid slice within array bounds
Root cause:Misunderstanding that slice ranges must be within the array length, causing runtime panic.
#2Having multiple mutable slices to the same array simultaneously.
Wrong approach:let slice1 = &mut arr[0..2]; let slice2 = &mut arr[1..3]; // error: cannot borrow mutably twice
Correct approach:let slice1 = &mut arr[0..3]; // use one mutable slice at a time
Root cause:Ignoring Rust's borrowing rules that prevent data races by allowing only one mutable reference.
#3Assuming slices can be resized like vectors.
Wrong approach:slice.push(10); // error: slices have no push method
Correct approach:let mut vec = vec![1, 2, 3]; vec.push(10); // vectors support resizing
Root cause:Confusing slices (fixed-size views) with vectors (growable collections).
Key Takeaways
Array slices in Rust are safe, efficient views into parts of arrays without copying data.
Slices are represented internally as fat pointers containing a pointer and a length, enabling runtime bounds checking.
Mutable slices allow safe modification of array parts while enforcing exclusive access to prevent data races.
Slices are fundamental for writing flexible, zero-copy functions that operate on array data.
Understanding slices unlocks deeper Rust concepts like borrowing, ownership, and memory safety.

Practice

(1/5)
1. What is an array slice in Rust?
easy
A. A new array created by copying elements
B. A macro to print arrays
C. A function that modifies an array
D. A reference to a part of an array without copying data

Solution

  1. Step 1: Understand what a slice is

    A slice is a reference to a contiguous part of an array, not a new array.
  2. Step 2: Compare options

    Only A reference to a part of an array without copying data correctly describes a slice as a reference without copying.
  3. Final Answer:

    A reference to a part of an array without copying data -> Option D
  4. Quick Check:

    Slice = reference to part of array [OK]
Hint: Remember: slices borrow, they don't copy [OK]
Common Mistakes:
  • Thinking slices create new arrays
  • Confusing slices with functions
  • Assuming slices own data
2. Which of the following is the correct syntax to create a slice from an array arr in Rust?
easy
A. let slice = &arr[1..4];
B. let slice = arr.slice(1, 4);
C. let slice = arr[1:4];
D. let slice = &arr(1..4);

Solution

  1. Step 1: Recall Rust slice syntax

    Rust uses &array[start..end] to create a slice referencing elements from start up to but not including end.
  2. Step 2: Check each option

    let slice = &arr[1..4]; uses correct Rust syntax. Options A, B, and C use invalid syntax.
  3. Final Answer:

    let slice = &arr[1..4]; -> Option A
  4. Quick Check:

    Slice syntax = &arr[start..end] [OK]
Hint: Use &array[start..end] for slices [OK]
Common Mistakes:
  • Using parentheses instead of brackets
  • Using colons instead of two dots
  • Calling slice as a method
3. What is the output of this Rust code?
let arr = [10, 20, 30, 40, 50];
let slice = &arr[1..4];
println!("{:?}", slice);
medium
A. [30, 40, 50]
B. [10, 20, 30]
C. [20, 30, 40]
D. [1, 4]

Solution

  1. Step 1: Understand the slice range

    The slice &arr[1..4] includes elements at indices 1, 2, and 3 (20, 30, 40).
  2. Step 2: Check the printed output

    Printing the slice shows the elements in the slice as an array-like list.
  3. Final Answer:

    [20, 30, 40] -> Option C
  4. Quick Check:

    Slice arr[1..4] = [20, 30, 40] [OK]
Hint: Remember slice excludes the end index element [OK]
Common Mistakes:
  • Including the element at index 4
  • Starting slice at index 0
  • Printing the slice as indices
4. Identify the error in this Rust code snippet:
let arr = [1, 2, 3, 4, 5];
let slice = &arr[2..6];
println!("{:?}", slice);
medium
A. The slice range goes out of bounds
B. The slice syntax is incorrect
C. The array is not mutable
D. The println! macro is used incorrectly

Solution

  1. Step 1: Check the slice range

    The array has length 5, valid indices are 0 to 4. The slice uses 2..6, which tries to access index 5 (out of bounds).
  2. Step 2: Identify the error type

    Rust will panic at runtime due to out-of-bounds slicing.
  3. Final Answer:

    The slice range goes out of bounds -> Option A
  4. Quick Check:

    Slice range must be within array length [OK]
Hint: Slice end index must be ≤ array length [OK]
Common Mistakes:
  • Using an end index beyond array length
  • Confusing syntax errors with runtime errors
  • Assuming arrays auto-expand
5. Given a function that takes a slice of integers and returns the sum of its elements, which of the following implementations is correct?
fn sum_slice(slice: &[i32]) -> i32 {
    // fill in
}
hard
A. slice.to_vec().sum()
B. slice.iter().sum()
C. slice[0] + slice[1]
D. slice.sum()

Solution

  1. Step 1: Understand slice methods

    A slice has an iterator via .iter(), and iterators have a .sum() method to add elements.
  2. Step 2: Evaluate each option

    slice.iter().sum() correctly uses slice.iter().sum(). slice.to_vec().sum() is valid because Vec<i32> has sum() method. slice[0] + slice[1] only sums two elements, not all. slice.sum() is invalid because slices don't have sum() directly.
  3. Final Answer:

    slice.iter().sum() -> Option B
  4. Quick Check:

    Use iter().sum() to sum slice elements [OK]
Hint: Use slice.iter().sum() to sum elements [OK]
Common Mistakes:
  • Trying to call sum() directly on slice
  • Summing only first two elements
  • Calling sum() on Vec<i32>
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard