ProgrammingRust Developer

How are slices implemented in Rust, and what are their differences from regular arrays and vectors in terms of memory management and safety?

Pass interviews with Hintsage AI assistant

Answer.

Background

Slices (slice, types [T] and &[T]) were introduced in Rust for safe and efficient access to subsets of arrays, vectors, and other sequences of elements. They allow avoiding allocations and copying data by providing just a "view" or window to a part of the collection. This contrasts with arrays, which have a fixed size determined at compile time, and dynamic collections, which hold a pointer and a length but own the memory.

Problem

When working with arrays and vectors in languages without strict lifetime control, errors like out of bounds, memory leaks, and the use of dangling pointers often occur. It is crucial to ensure that while working with subsets of collections, copying does not happen and memory safety is not compromised, which is particularly relevant at the system level.

Solution

In Rust, a slice is a "pointer + length" to a part of the data that does not own the contents. They are always accompanied by a lifetime, and the compiler ensures that the slice does not outlive the original (array, Vec, String). All operations with slices are done through safe access methods, and any out-of-bounds access results in a panic at runtime.

Example code:

let arr = [1, 2, 3, 4, 5]; let slice = &arr[1..4]; // [2,3,4] type: &[i32] let mut vec = vec![10, 20, 30]; let mut_slice: &mut [i32] = &mut vec[..2]; mut_slice[0] = 99; assert_eq!(vec, [99, 20, 30]);

Key features:

  • A slice does not own the data and is always valid only as long as the source data is alive.
  • Out-of-bounds access causes a panic or compile error, which is safe to handle.
  • Support for immutable and mutable slices (immutable - read-only, mutable - allows changing data in the source).

Trick questions.

Can you create a slice that exceeds the size of the original array or vector?

No. The compiler and runtime guarantee that slices can be created only at valid indices of the original data. Attempting to go out of bounds will cause a panic.

let arr = [1, 2, 3]; let s = &arr[0..4]; // panic at runtime

Are slices independent owners of memory?

No. Slices are merely a "window" to the data; they do not own the memory. Attempting to return a slice from a function if the source is local will lead to a compilation error.

fn give_slice() -> &[i32] { let arr = [1,2,3]; &arr[1..] } // error: arr does not live long enough

What are the differences between slices and arrays in Rust at the type and operation level?

An array has a fixed length known at compile time and is fully allocated on the stack. A slice can have any length, dynamically determined, and always stores a pointer and length.

let a: [u32; 3] = [1,2,3]; // Fixed-length array let s: &[u32] = &a[..]; // Slice of any size

Common mistakes and anti-patterns

  • Attempting to return a slice to a local array from a function leads to lifetime errors.
  • Mixing ownership of slices and original collections (double free, incorrect access when reconstructing collections).

Real-life example

Negative case

A programmer returned a slice from a function where a local array was created. After exiting, the original function was deleted, and the slice became a "dangling" pointer. This caused a bug and even a crash.

Pros:

  • Simple, if you don’t think about lifetimes.

Cons:

  • Possible UB.
  • Does not compile in Rust.

Positive case

A slice is always created as a reference to external data, where the owner of the data and the slice live for the same duration. The compiler ensures a tight coupling of lifetimes between the slice and the source.

Pros:

  • Safety guarantee.
  • No "dangling" pointers.
  • Easy to break large arrays into safe parts.

Cons:

  • Need to carefully think about the architecture of data lifetimes.