BBuzzard

A slice &[T] is a view into a run of elements owned by something else: a pointer and a length. &v[1..4] borrows that span. Because the length travels with the pointer, &[T] is a "fat" pointer, twice the size of a plain one.

&str is a slice of UTF-8 bytes. String is the owned, growable buffer behind it, and a string literal is a &'static str. Byte ranges on &str must land on char boundaries or they panic; chars(), char_indices(), and get() are safer when the text is not ASCII.

Take &[T] and &str in function parameters: callers can then pass arrays, Vecs, Strings, and literals.

Slicing arrays and Vecs

fn sum(nums: &[i32]) -> i32 {
    nums.iter().sum()
}

fn demo_slices() {
    println!("-- slices --");

    let v = vec![1, 2, 3, 4, 5];
    println!("{} {:?} {:?} {:?}", sum(&v[1..4]), &v[..2], &v[3..], &v[..]);

    let arr = [10, 20, 30];
    println!("array and vec both coerce: {} {}", sum(&arr), sum(&v));
    println!("fat pointer bytes {} thin {}", size_of::<&[i32]>(), size_of::<&i32>());
    println!("empty {:?} len {}", &v[2..2], v[2..2].len());
}

Slice methods

fn demo_slice_methods() {
    println!("-- slice methods --");

    let s = [3, 1, 4, 1, 5, 9, 2, 6];
    println!("first {:?} last {:?} get {:?}", s.first(), s.last(), s.get(100));
    println!(
        "contains 9? {} position of 1 {:?}",
        s.contains(&9),
        s.iter().position(|&n| n == 1)
    );
    println!("starts_with {} ends_with {}", s.starts_with(&[3, 1]), s.ends_with(&[6]));

    let (head, tail) = s.split_first().unwrap();
    println!("head {head} tail {tail:?}");

    let parts: Vec<_> = s.split(|&n| n == 1).collect();
    println!("split on 1 {parts:?}");
    println!("chunks_exact {:?}", s.chunks_exact(3).collect::<Vec<_>>());
    println!(
        "concat {:?} join {:?}",
        [[1, 2], [3, 4]].concat(),
        ["a", "b", "c"].join("-")
    );
}

Mutable slices change the owner's data

fn demo_mut_slices() {
    println!("-- mut slices --");

    let mut v = vec![5, 4, 3, 2, 1];
    let middle = &mut v[1..4];
    middle.sort();
    println!("{v:?}");
    v[..2].fill(0);
    v.swap(0, 4);
    println!("{v:?}");
    v.rotate_left(1);
    println!("rotated {v:?}");

    let mut buf = [0u8; 4];
    buf[1..3].copy_from_slice(&[7, 8]);
    println!("copied {buf:?}");
}

&str and String

fn first_word(s: &str) -> &str {
    match s.find(' ') {
        Some(i) => &s[..i],
        None => s,
    }
}

fn demo_str_slices() {
    println!("-- str slices --");

    let owned = String::from("hello rust");
    println!("{} {}", first_word(&owned), first_word("only"));

    let literal: &'static str = "rustacean";
    println!("{} {}", &literal[0..4], &literal[4..]);

    let as_slice: &str = owned.as_str();
    println!(
        "as_str {as_slice} same bytes? {}",
        std::ptr::eq(as_slice.as_ptr(), owned.as_ptr())
    );
}

char boundaries

fn demo_char_boundaries() {
    println!("-- char boundaries --");

    let s = "héllo";
    println!("bytes {} chars {}", s.len(), s.chars().count());
    println!("is_char_boundary(2)? {}", s.is_char_boundary(2));

    // &s[0..2] would panic: byte index 2 is inside 'é'.
    println!("get(0..2) {:?} get(0..3) {:?}", s.get(0..2), s.get(0..3));

    for (i, c) in s.char_indices() {
        print!("{i}:{c} ");
    }

    println!();

    // The first n chars, done safely.
    let first3: String = s.chars().take(3).collect();
    println!("first 3 chars {first3}");
}

Slicing into structured text

fn demo_parsing_with_slices() {
    println!("-- parsing --");

    let line = "key=value; other=42";
    for part in line.split(';') {
        if let Some((k, v)) = part.trim().split_once('=') {
            println!("{k} -> {v}");
        }
    }

    let path = "/usr/local/bin";
    println!("strip {:?} {:?}", path.strip_prefix("/usr"), path.strip_suffix("/lib"));

    let ext = "photo.tar.gz".rsplit_once('.').map(|(_, e)| e);
    println!("ext {ext:?}");
}
$ cargo run --bin 13_slices
-- slices --
9 [1, 2] [4, 5] [1, 2, 3, 4, 5]
array and vec both coerce: 60 15
fat pointer bytes 16 thin 8
empty [] len 0
-- slice methods --
first Some(3) last Some(6) get None
contains 9? true position of 1 Some(1)
starts_with true ends_with true
head 3 tail [1, 4, 1, 5, 9, 2, 6]
split on 1 [[3], [4], [5, 9, 2, 6]]
chunks_exact [[3, 1, 4], [1, 5, 9]]
concat [1, 2, 3, 4] join "a-b-c"
-- mut slices --
[5, 2, 3, 4, 1]
[1, 0, 3, 4, 0]
rotated [0, 3, 4, 0, 1]
copied [0, 7, 8, 0]
-- str slices --
hello only
rust acean
as_str hello rust same bytes? true
-- char boundaries --
bytes 6 chars 5
is_char_boundary(2)? false
get(0..2) None get(0..3) Some("hé")
0:h 1:é 3:l 4:l 5:o 
first 3 chars hél
-- parsing --
key -> value
other -> 42
strip Some("/local/bin") None
ext Some("gz")

Full program

13_slices/main.rs · 149 lines
View on GitHub
// 13. Slices and string slices
//
// A slice &[T] is a view into a run of elements owned by something else: a pointer and
// a length. &v[1..4] borrows that span. Because the length travels with the pointer,
// &[T] is a "fat" pointer, twice the size of a plain one.
//
// &str is a slice of UTF-8 bytes. String is the owned, growable buffer behind it, and a
// string literal is a &'static str. Byte ranges on &str must land on char boundaries or
// they panic; chars(), char_indices(), and get() are safer when the text is not ASCII.
//
// Take &[T] and &str in function parameters: callers can then pass arrays, Vecs,
// Strings, and literals.
//
// Run: cargo run --bin 13_slices

fn sum(nums: &[i32]) -> i32 {
    nums.iter().sum()
}

// Example 1: slicing arrays and Vecs.
fn demo_slices() {
    println!("-- slices --");

    let v = vec![1, 2, 3, 4, 5];
    println!("{} {:?} {:?} {:?}", sum(&v[1..4]), &v[..2], &v[3..], &v[..]);

    let arr = [10, 20, 30];
    println!("array and vec both coerce: {} {}", sum(&arr), sum(&v));
    println!("fat pointer bytes {} thin {}", size_of::<&[i32]>(), size_of::<&i32>());
    println!("empty {:?} len {}", &v[2..2], v[2..2].len());
}

// Example 2: slice methods.
fn demo_slice_methods() {
    println!("-- slice methods --");

    let s = [3, 1, 4, 1, 5, 9, 2, 6];
    println!("first {:?} last {:?} get {:?}", s.first(), s.last(), s.get(100));
    println!(
        "contains 9? {} position of 1 {:?}",
        s.contains(&9),
        s.iter().position(|&n| n == 1)
    );
    println!("starts_with {} ends_with {}", s.starts_with(&[3, 1]), s.ends_with(&[6]));

    let (head, tail) = s.split_first().unwrap();
    println!("head {head} tail {tail:?}");

    let parts: Vec<_> = s.split(|&n| n == 1).collect();
    println!("split on 1 {parts:?}");
    println!("chunks_exact {:?}", s.chunks_exact(3).collect::<Vec<_>>());
    println!(
        "concat {:?} join {:?}",
        [[1, 2], [3, 4]].concat(),
        ["a", "b", "c"].join("-")
    );
}

// Example 3: mutable slices change the owner's data.
fn demo_mut_slices() {
    println!("-- mut slices --");

    let mut v = vec![5, 4, 3, 2, 1];
    let middle = &mut v[1..4];
    middle.sort();
    println!("{v:?}");
    v[..2].fill(0);
    v.swap(0, 4);
    println!("{v:?}");
    v.rotate_left(1);
    println!("rotated {v:?}");

    let mut buf = [0u8; 4];
    buf[1..3].copy_from_slice(&[7, 8]);
    println!("copied {buf:?}");
}

fn first_word(s: &str) -> &str {
    match s.find(' ') {
        Some(i) => &s[..i],
        None => s,
    }
}

// Example 4: &str and String.
fn demo_str_slices() {
    println!("-- str slices --");

    let owned = String::from("hello rust");
    println!("{} {}", first_word(&owned), first_word("only"));

    let literal: &'static str = "rustacean";
    println!("{} {}", &literal[0..4], &literal[4..]);

    let as_slice: &str = owned.as_str();
    println!(
        "as_str {as_slice} same bytes? {}",
        std::ptr::eq(as_slice.as_ptr(), owned.as_ptr())
    );
}

// Example 5: char boundaries.
fn demo_char_boundaries() {
    println!("-- char boundaries --");

    let s = "héllo";
    println!("bytes {} chars {}", s.len(), s.chars().count());
    println!("is_char_boundary(2)? {}", s.is_char_boundary(2));

    // &s[0..2] would panic: byte index 2 is inside 'é'.
    println!("get(0..2) {:?} get(0..3) {:?}", s.get(0..2), s.get(0..3));

    for (i, c) in s.char_indices() {
        print!("{i}:{c} ");
    }

    println!();

    // The first n chars, done safely.
    let first3: String = s.chars().take(3).collect();
    println!("first 3 chars {first3}");
}

// Example 6: slicing into structured text.
fn demo_parsing_with_slices() {
    println!("-- parsing --");

    let line = "key=value; other=42";
    for part in line.split(';') {
        if let Some((k, v)) = part.trim().split_once('=') {
            println!("{k} -> {v}");
        }
    }

    let path = "/usr/local/bin";
    println!("strip {:?} {:?}", path.strip_prefix("/usr"), path.strip_suffix("/lib"));

    let ext = "photo.tar.gz".rsplit_once('.').map(|(_, e)| e);
    println!("ext {ext:?}");
}

fn main() {
    demo_slices();
    demo_slice_methods();
    demo_mut_slices();
    demo_str_slices();
    demo_char_boundaries();
    demo_parsing_with_slices();
}