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`.
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:
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:
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:
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
- Write `fn first_word(s: &str) -> &str` returning the first space-separated word
- Find a Vec<i32>'s max via `fn max(v: &[i32]) -> i32`
- Compare `.chars().count()` vs `.len()` on a Korean string to see the byte/char mismatch
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗