Slices
Reviewed & published by Brayan K
Rust is a systems programming language focused on speed, memory safety, and fearless concurrency — and slices let you reference part of a collection without copying it.
Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll create string slices (&str) and array slices (&[T]), and write functions that return borrowed views into data.
What You'll Learn in This Lesson
- What a slice is and why it borrows data
- Creating string slices with &s[start..end]
- Range shorthand: [..n], [n..], and [..]
- Slicing arrays and vectors with &[T]
- Why functions prefer &str over &String
- Returning a slice that borrows from input
1️⃣ String Slices
A string slice, written &str, references a portion of a String. You create one with a range: &s[0..5] borrows the first five bytes. The end index is exclusive, and you can drop the 0 or the end for handy shorthands.
fn main() {
let s = String::from("hello world");
// A string slice &str borrows part of a String. The range is [start..end],
// where end is EXCLUSIVE. Indices are byte positions.
let hello = &s[0..5]; // bytes 0,1,2,3,4 -> "hello"
let world = &s[6..11]; // bytes 6..10 -> "world"
println!("{} / {}", hello, world); // -> hello / world
// Shorthand: omit 0 at the start, or omit the end to go to the finish.
let start = &s[..5]; // same as [0..5]
let end = &s[6..]; // from 6 to the end
let whole = &s[..]; // the entire string
println!("{} | {} | {}", start, end, whole);
}No data was copied here — each slice is a lightweight reference into the original s. That's why slices are so cheap to pass around.
2️⃣ Slices in Functions and on Arrays
Slices shine as function parameters and return types. A function returning the first word can hand back a &str that borrows the input — no allocation. The same idea applies to arrays: &[i32] is a slice of integers, letting one function work on a whole array or just part of it.
fn main() {
let sentence = String::from("the quick brown fox");
// first_word returns a slice that BORROWS from sentence — no copying.
let word = first_word(&sentence);
println!("First word: {}", word); // -> First word: the
// Slices also work on arrays. &[i32] is a slice of i32 values.
let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4]; // -> [20, 30, 40]
println!("Middle: {:?}", middle);
println!("Sum of middle: {}", sum(middle)); // -> Sum of middle: 90
}
// Taking &str (not &String) means this works for String AND literals.
fn first_word(text: &str) -> &str {
let bytes = text.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' { // b' ' is the byte value of a space
return &text[..i]; // slice up to the first space
}
}
&text[..] // no space: the whole string is one word
}
fn sum(slice: &[i32]) -> i32 {
let mut total = 0;
for n in slice {
total += n;
}
total
}Because first_word takes &str, it accepts a String, a literal, or another slice. And sum works on any slice of i32, whether from an array or a vector.
Your turn. Fill in the blanks marked ___, then run it.
// 🎯 YOUR TURN — finish the slices, then run it.
fn main() {
let text = String::from("rust is fun");
// 1) Slice out the first word "rust" (bytes 0 up to but not including 4).
let first = &text[0..___]; // 👉 the exclusive end index for "rust"
println!("first = {}", first); // expected: first = rust
// 2) Slice from byte 8 to the end to get "fun".
let last = &text[___..]; // 👉 the start index for "fun"
println!("last = {}", last); // expected: last = fun
// 3) Sum a slice of the middle three numbers.
let nums = [1, 2, 3, 4, 5];
let mid = &nums[1..4]; // [2, 3, 4]
let total: i32 = mid.iter().sum();
println!("total = {}", total); // expected: total = 9
}
// ✅ Expected output:
// first = rust
// last = fun
// total = 9Mini-Challenge: A Text Inspector
Write a last_word function that returns a slice of the final word. Run it with cargo run.
// 🎯 MINI-CHALLENGE: a tiny text inspector.
//
// 1. Write fn last_word(text: &str) -> &str that returns the slice AFTER the
// last space (the final word). Hint: scan for the last b' ' and slice from
// just after it; if there is no space, return the whole text.
// 2. In main, call it on "hello there world" and print the result.
// 3. Also print the length of that last word with .len().
//
// ✅ Example output:
// Last word: world
// Length: 5
fn main() {
// your code here
}Common Errors (and the fix)
- "byte index is not a char boundary" — You sliced through the middle of a multi-byte UTF-8 character. Slice on character boundaries, or use methods like .chars() for non-ASCII text.
- "byte index out of bounds" — The end of your range is past the string's length. Check with .len() first.
- "cannot borrow as mutable" — Modifying a String (e.g. .clear()) while a slice of it is still in use breaks the borrow rules. Finish with the slice first.
- Returning &String unnecessarily — Prefer &str for flexibility; a String coerces to &str automatically.
Pro Tips
- 💡 Accept &str, &[T]: writing parameters as slices makes functions work on the widest range of inputs.
- 💡 Slices are zero-copy: reach for them to avoid unnecessary allocations.
- 💡 String literals are slices: "hello" has type &'static str — a slice into the program's binary.
📋 Quick Reference — Slices
| Goal | Rust Syntax |
|---|---|
| String slice | &s[0..5] |
| From start | &s[..5] |
| To end | &s[6..] |
| Whole thing | &s[..] |
| Array slice | &arr[1..4] |
| Slice param type | &str / &[i32] |
🎉 Lesson Complete!
- ✅ A slice borrows a contiguous part of a collection without copying
- ✅ &str slices strings; &[T] slices arrays and vectors
- ✅ Range ends are exclusive; [..n], [n..], [..] are shorthands
- ✅ Prefer &str parameters for maximum flexibility
Practice quiz
What is a slice in Rust?
- A copy of a collection
- A new heap allocation
- A reference to a contiguous run of elements
- A type of loop
Answer: A reference to a contiguous run of elements. A slice borrows a contiguous view into existing data without copying.
What type is a string slice written as?
- &str
- String
- char
- Vec<u8>
Answer: &str. A string slice has type &str.
Given s = "hello world", what is &s[0..5]?
- world
- hello world
- ello
- hello
Answer: hello. Bytes 0 through 4 form "hello"; the end index is exclusive.
Is the end index of a slice range inclusive or exclusive?
- Inclusive
- Exclusive
- Depends on the type
- It is the length
Answer: Exclusive. The end of a slice range is exclusive, so &s[0..5] excludes index 5.
What does the shorthand &s[..] produce?
- The entire string
- An empty slice
- The first char
- An error
Answer: The entire string. Omitting both ends slices the whole thing.
Why should functions take &str instead of &String?
- It is faster to type
- &String is invalid
- &str accepts literals, slices, and Strings
- It avoids the heap
Answer: &str accepts literals, slices, and Strings. &str is more general; a String coerces to &str automatically.
What is the type of a slice of i32 values?
- [i32]
- &[i32]
- Vec<i32>
- i32
Answer: &[i32]. An array slice of integers is &[i32].
Does creating a slice copy the underlying data?
- Yes, always
- Only for strings
- Only for arrays
- No, it just borrows a view
Answer: No, it just borrows a view. A slice is a pointer plus length; it borrows without copying.
What does &nums[1..4] give for nums = [10,20,30,40,50]?
- [10, 20, 30]
- [20, 30, 40]
- [20, 30, 40, 50]
- [30, 40]
Answer: [20, 30, 40]. Indices 1, 2, 3 (end exclusive) are 20, 30, 40.
What happens if you slice through the middle of a multi-byte UTF-8 character?
- It rounds down
- It returns None
- The program panics
- Nothing
Answer: The program panics. String slices must land on char boundaries or the program panics.
Continue this course
- Previous: References & Borrowing
- Next: Structs — Group related data together and attach behaviour with methods
- Quick reference: Rust cheat sheet › Ownership & Borrowing
Frequently asked questions
What is a slice and why is it useful?
A slice is a reference to a contiguous run of elements inside a collection, rather than a copy of them. A string slice &str references part of a String's text; an array slice &[T] references part of an array or vector. Slices are useful because they let you pass around a window into data without copying it and without taking ownership — for example, returning the first word of a string as a slice that borrows from the original.
Why should functions take &str instead of &String?
A &str is more general than a &String. If a function accepts &str, you can pass it a string literal (which is already a &str), a slice of a String, or a whole String (which coerces to &str automatically). If it only accepted &String, you could not pass a literal. So taking &str makes your function usable in more situations with no downside, which is why it is the idiomatic choice for read-only string parameters.
Are slice ranges inclusive or exclusive at the end?
The end of a slice range is exclusive: &s[0..5] takes bytes at indices 0, 1, 2, 3, and 4 — five characters — but not index 5. You can omit the start (it defaults to 0) or the end (it defaults to the length), so &s[..5], &s[6..], and &s[..] are all valid shorthands. If you slice in the middle of a multi-byte UTF-8 character, the program will panic, because string slices must land on character boundaries.
Does slicing copy the data?
No. A slice is just a reference — internally a pointer to the starting element plus a length. Creating a slice does not allocate or copy the underlying data; it simply borrows a view of it. That makes slices very cheap and is why returning a slice from a function is a zero-cost way to hand back part of an existing value. Because it borrows, the original data must outlive the slice.