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.
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.
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:
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
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:
Cons:
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:
Cons: