← Back to Rust series
🦀
Ownership
Ownership · Prerequisite: lesson 07

08. Slices — &str and &[T]

A slice is a reference view into part of a collection. `&str` is a view into a String or into a static string literal; `&[T]` is a view into a Vec or array. Slices supercharge function signatures and explain why `&str` is the idiomatic argument type rather than `String`.

Rustslice&str&[T]referenceString
Duration
~1 hour
Level
📊 Intermediate
Prerequisite
🎯 Lesson 07
OUTCOME
A slice is a reference view into part of a collection. `&str` is a view into a String or into a static string literal; `&[T]` is a view into a Vec or array. Slices supercharge function signatures and explain why `&str` is the idiomatic argument type rather than `String`.

What you'll learn

  • 1Create and index `&str` and `&[T]` slices
  • 2Accept `&str` arguments to increase caller flexibility
  • 3Know slices are fat pointers (pointer + length)
  • 4Know string literals are `&'static str`
  • 5Identify where wrong slice ranges panic at runtime

Overview

A function that takes `String` forces the caller to allocate. Worse, you can't pass a string literal `"hello"` directly. Slices solve this — accept `&str` and both Strings and literals work.

Core Concepts

1) Slice = reference view

A slice is a fat pointer carrying both **data pointer + length** (16 bytes: pointer 8 + length 8). It does not own data, it borrows.

2) &str — string slice

  • A string literal `"hello"` has type `&'static str` — alive for the program's entire lifetime
  • Sub-slice a String with `&s[0..5]`
  • Slicing at a non-**UTF-8 boundary** panics at runtime

3) &[T] — array / Vec slice

  • `&v[1..4]` for a partial slice
  • A function `fn sum(v: &[i32])` accepts arrays and Vecs alike

4) Function signature idiom

When designing a library function, prefer **`&str` over `String`, `&[T]` over `Vec<T>`** as arguments. Callers can pass more shapes, and unnecessary clones disappear.

Hands-on Examples

A function that accepts both Strings and literals — via &str:

rust
fn shout(s: &str) {
    println!("{}!", s.to_uppercase());
}

fn main() {
    let owned = String::from("hello");
    shout(&owned);   // String → &str via Deref
    shout("world"); // literal passes directly
}

Building and indexing slices:

rust
fn main() {
    let s = String::from("hello world");
    let hello: &str = &s[0..5];
    let world: &str = &s[6..11];
    println!("{} / {}", hello, world);

    let arr = [10, 20, 30, 40, 50];
    let mid: &[i32] = &arr[1..4]; // [20, 30, 40]
    println!("{:?}", mid);
}

A generic sum over slices:

rust
fn sum(v: &[i32]) -> i32 {
    let mut s = 0;
    for x in v { s += x; }
    s
}

fn main() {
    let v = vec![1, 2, 3, 4, 5];
    println!("{}", sum(&v));       // Vec → &[i32]
    println!("{}", sum(&[10, 20])); // array → &[i32]
    println!("{}", sum(&v[1..4]));   // slice
}

Common Mistakes

Q. Slicing a Korean string panics

A. In UTF-8 a Korean character is 3 bytes. `&"안녕"[0..1]` slices in the middle of a character → panic. For per-character iteration use `.chars()`.

Q. Can I take a `String` argument?

A. You can, but `&str` is more general. Take `String` only when your function genuinely needs to own the data (e.g., to store it).

Q. Is the length known at compile time?

A. No — slice length is runtime, which is why slices carry the length inline (fat pointer). They're twice the size of a thin reference.

Recap

  • A slice is a (pointer, length) reference view
  • &str / &[T] for strings / arbitrary types
  • Take &str·&[T] in function signatures to accept more callers naturally
  • Slicing at a non-UTF-8 boundary panics at runtime

Try It Yourself

  1. Write `fn first_word(s: &str) -> &str` returning the first space-separated word
  2. Find a Vec<i32>'s max via `fn max(v: &[i32]) -> i32`
  3. Compare `.chars().count()` vs `.len()` on a Korean string to see the byte/char mismatch
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗