iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Slices

A slice is a view into a contiguous sequence — an array, vector, or string. It’s a fat pointer (data + length) that borrows from its source. Slices are how Rust gives you read or write access to part of a collection without copying or transferring ownership.

&[T], &mut [T], &str, ranges

EXAMPLE
// 1) Slicing an array
fn main() {
    let arr = [10, 20, 30, 40, 50];
    let whole: &[i32] = &arr[..];
    let head:  &[i32] = &arr[..3];        // 10, 20, 30
    let tail:  &[i32] = &arr[2..];        // 30, 40, 50
    let mid:   &[i32] = &arr[1..4];        // 20, 30, 40

    println!("{:?} {:?} {:?} {:?}", whole, head, tail, mid);
}

// 2) Slicing a Vec
fn main2() {
    let v: Vec<i32> = (1..=10).collect();
    let first_five: &[i32] = &v[..5];      // [1, 2, 3, 4, 5]
    let after_two: &[i32] = &v[2..];        // [3, 4, 5, 6, 7, 8, 9, 10]
    println!("len={} first_five={:?}", v.len(), first_five);
}

// 3) Functions that accept slices — better than Vec<T>
fn sum(xs: &[i32]) -> i32 {            // accepts &Vec<i32>, &[i32; N], &mut [i32]
    xs.iter().sum()
}

fn main3() {
    let v = vec![1, 2, 3, 4];
    let a = [10, 20, 30];
    println!("{} {}", sum(&v), sum(&a));    // 10  60
}

// 4) Mutable slices
fn double_in_place(xs: &mut [i32]) {
    for x in xs.iter_mut() { *x *= 2; }
}

fn main4() {
    let mut v = vec![1, 2, 3];
    double_in_place(&mut v);
    println!("{:?}", v);                    // [2, 4, 6]
}

// 5) String slices — &str
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &b) in bytes.iter().enumerate() {
        if b == b' ' { return &s[..i]; }
    }
    s
}

fn main5() {
    let owned: String = String::from("hello world");
    let w: &str = first_word(&owned);       // borrows from owned
    println!("{}", w);                       // 'hello'
}

// CAREFUL: byte slicing &s[..i] panics if i is not on a UTF-8 char boundary.
// Use s.char_indices() or s.split_whitespace() for safe text iteration.

// 6) Iteration patterns
fn examples(v: &[i32], v2: &mut [i32]) {
    for x in v { /* &i32 */ }
    for x in v.iter() { /* &i32 */ }
    for x in v2.iter_mut() { *x += 1; }     // &mut i32
    for (i, x) in v.iter().enumerate() { /* */ }
    let total: i32 = v.iter().sum();
    let evens: Vec<&i32> = v.iter().filter(|x| **x % 2 == 0).collect();
}

// 7) Split into halves — useful for divide and conquer + no overlap aliasing
fn main7() {
    let mut v = vec![1, 2, 3, 4, 5, 6];
    let (left, right) = v.split_at_mut(3);
    left[0]  = 100;
    right[0] = 200;
    // No borrow checker fight: split_at_mut hands out non-overlapping &mut slices.
    println!("{:?}", v);                     // [100, 2, 3, 200, 5, 6]
}

// 8) Windows and chunks — overlap or non-overlap
fn main8() {
    let v = vec![1, 2, 3, 4, 5];
    for w in v.windows(3) { println!("{:?}", w); }   // [1,2,3] [2,3,4] [3,4,5]
    for c in v.chunks(2)  { println!("{:?}", c); }   // [1,2] [3,4] [5]
}

// 9) Slice as a fat pointer
//   &[T]    = (data: *const T, len: usize)        — 16 bytes on 64-bit
//   &mut [T] = (data: *mut T,   len: usize)
//
// You can construct one from a raw pointer (unsafe):
use std::slice;
fn from_raw_unsafe<'a>(ptr: *const i32, len: usize) -> &'a [i32] {
    unsafe { slice::from_raw_parts(ptr, len) }
}

// 10) Out-of-bounds — panics by default, use .get() for non-panicking
fn main10() {
    let v = vec![10, 20, 30];
    let _ = v[5];                            // panic: index out of bounds
    if let Some(x) = v.get(5) { /* */ }       // None — safe
}

// 11) Slice methods cheat sheet
//   len, is_empty, first, last, get(i)
//   iter, iter_mut, into_iter (for owned)
//   contains(&x), binary_search(&x)
//   sort, sort_by, sort_unstable                — in-place
//   reverse, fill(v), rotate_left(n), rotate_right(n)
//   join(sep), concat
//   split, splitn, rsplit, split_at_mut
//   windows(n), chunks(n), chunks_exact(n)
//   copy_from_slice, clone_from_slice            — bulk assignment
//
// And via traits:
//   to_vec()  — owned copy
//   to_owned() — generic
//   chunks_exact_mut(n).remainder()

// 12) Lifetimes — slices borrow from their source
fn longest<'a>(a: &'a [i32], b: &'a [i32]) -> &'a [i32] {
    if a.len() >= b.len() { a } else { b }
}
// Caller's data must outlive both inputs. The compiler enforces it.

// 13) When to take &str vs &String vs String
//   • Function parameter: &str          ← accepts &String automatically via deref coercion
//   • Field that owns:    String
//   • Return type for slicing into self: &str

// 14) Common bugs
//   • &arr[5..3]                          → panic 'slice index starts after end'
//   • s[..i] where i is mid-UTF-8         → panic 'byte index N is not a char boundary'
//   • Multiple &mut slices of the same range → split_at_mut to satisfy the borrow checker
//   • Storing a &[T] longer than the source → 'borrowed value does not live long enough'
//   • Using vec.iter() and trying to mutate items → use iter_mut()
//   • &Vec<T> instead of &[T] as a param → less flexible, accepts only Vec

Why it matters

A slice is a borrowed view, not an owned collection. Take &[T] and &str in function signatures so callers can pass Vec, array, or owned String equally well, and reach for split_at_mut, windows, or chunks when the borrow checker is stopping you from carving up a slice the way you need.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
let v = vec![1, 2, 3, 4, 5];
let middle: &[i32] = &v[1..4];
for x in middle { println!("{x}"); }
Try it Yourself »

Discussion

Loading…