Question
I cannot find in the Vec<T> documentation how to retrieve a slice from a specific range.
Is there something like this in Rust’s standard library?
let a = vec![1, 2, 3, 4];
let suba = a.subvector(0, 2); // should contain [1, 2]
Short Answer
By the end of this page, you will understand how to get a slice from a Vec<T> in Rust, how range syntax works, the difference between borrowing a slice and creating a new vector, and how to avoid common indexing mistakes.
Concept
In Rust, a Vec<T> is a growable heap-allocated collection, while a slice (&[T]) is a borrowed view into a sequence of elements.
When you want part of a vector, Rust usually does not create a new vector automatically. Instead, it gives you a slice that points to a section of the existing vector.
For example:
let a = vec![1, 2, 3, 4];
let suba = &a[0..2];
Here, suba is a slice of type &[i32], and it refers to the first two elements: [1, 2].
This matters because:
- It avoids unnecessary copying.
- It is efficient.
- It fits Rust’s ownership and borrowing model.
- It allows functions to work with both arrays and vectors using the same slice type.
If you actually need an independent collection, you can convert the slice into a new Vec<T>:
let a = vec![1, 2, 3, 4];
let suba = a[0..2].to_vec();
Now suba is a new Vec<i32> containing copied values.
Mental Model
Think of a Vec<T> as a full loaf of bread, and a slice as a few pieces cut from it.
- The vector owns the whole loaf.
- A slice is just a view of part of that loaf.
- Taking a slice does not bake a new loaf.
- If you want a separate loaf, you must copy the pieces into a new vector.
So &a[0..2] means: “Let me look at elements starting at index 0 and stopping before index 2.”
Syntax and Examples
Rust uses range syntax with indexing to create slices.
Basic syntax
let slice = &vec[start..end];
startis includedendis excluded
So 0..2 means indices 0 and 1.
Example: get the first two elements
let a = vec![1, 2, 3, 4];
let suba = &a[0..2];
println!("{:?}", suba); // [1, 2]
suba has type:
&[i32]
Example: from a starting index to the end
let a = vec![1, 2, 3, 4];
let tail = &a[2..];
println!("{:?}", tail); // [3, 4]
Example: from the beginning up to an index
let a = vec![1, 2, 3, 4];
let head = &a[..2];
println!("{:?}", head); // [1, 2]
Example: full slice
let a = vec![1, 2, 3, 4];
let all = &a[..];
println!("{:?}", all); // [1, 2, 3, 4]
Example: create a new vector instead of a slice
let a = vec![1, 2, 3, 4];
let copied = a[1..3].to_vec();
println!("{:?}", copied); // [2, 3]
Use this when you need ownership of the extracted elements rather than just a borrowed view.
Step by Step Execution
Consider this example:
let a = vec![10, 20, 30, 40];
let part = &a[1..3];
println!("{:?}", part);
Step by step:
-
let a = vec![10, 20, 30, 40];- A vector is created with four elements.
- Index positions are:
0 -> 101 -> 202 -> 303 -> 40
-
let part = &a[1..3];- Rust reads the range
1..3. - Start at index
1. - Stop before index
3. - That includes elements at indices
1and2. partbecomes a slice referencing[20, 30].
- Rust reads the range
-
println!("{:?}", part);- Rust prints the slice.
- Output is:
[20, 30]
Important detail
The original vector a still owns the data. part only borrows it.
That means part cannot outlive a. If a is dropped, the slice becomes invalid, so Rust prevents that at compile time.
Real World Use Cases
Slicing vectors is useful in many real programs.
Processing part of a dataset
let readings = vec![12, 15, 18, 20, 22, 25];
let recent = &readings[3..];
You might only want the most recent sensor readings.
Pagination or batching
let users = vec!["a", "b", "c", "d", "e"];
let page = &users[0..2];
This can represent one page of results or one batch of records.
Parsing tokens
let tokens = vec!["let", "x", "=", "5", ";"];
let expression = &tokens[1..4];
Compilers and parsers often work with windows into existing data.
Working with function parameters
Many Rust functions accept slices instead of vectors:
fn sum(values: &[i32]) -> i32 {
values.iter().sum()
}
let numbers = vec![1, 2, 3, 4];
println!("{}", sum(&numbers[1..3])); // 5
This is common because slices are flexible and efficient.
Real Codebase Usage
In real Rust codebases, developers often prefer slices over Vec<T> in APIs when ownership is not needed.
Accept slices in functions
fn average(values: &[f64]) -> f64 {
let total: f64 = values.iter().sum();
total / values.len() as f64
}
This lets callers pass:
- a full vector:
&v - part of a vector:
&v[1..4] - an array slice:
&arr[..]
Validation before slicing
Direct indexing can panic if the range is invalid. In production code, developers often validate lengths first.
fn first_two(values: &[i32]) -> Option<&[i32]> {
if values.len() >= 2 {
Some(&values[..2])
} else {
None
}
}
This is a simple guard clause pattern.
Using .get() for safer access
let values = vec![1, 2, 3, 4];
if let Some(part) = values.get(1..3) {
println!("{:?}", part);
}
get() returns Option<&[T]> instead of panicking.
Cloning only when needed
If a function needs ownership, developers often borrow first and copy later only when necessary:
let values = vec![1, 2, 3, 4];
let owned_part = values[1..3].to_vec();
This avoids unnecessary allocation in the common case.
Common Mistakes
1. Expecting a new Vec<T> instead of a slice
This code does not create a new vector:
let a = vec![1, 2, 3, 4];
let suba = &a[0..2];
suba is a slice, not a Vec<i32>.
If you need a new vector:
let suba = a[0..2].to_vec();
2. Forgetting that the end index is excluded
Broken expectation:
let a = vec![1, 2, 3, 4];
let suba = &a[0..2]; // contains [1, 2], not [1, 2, 3]
Rule:
startis includedendis excluded
3. Using an out-of-bounds range
Broken code:
let a = vec![1, 2, 3];
let suba = &a[0..5];
This will panic at runtime.
Safer version:
let a = vec![1, 2, 3];
let suba = a.get(0..5);
println!("{:?}", suba); // None
4. Trying to modify the vector while a slice is borrowed
Broken code:
let mut a = vec![1, 2, 3, 4];
let suba = &a[0..2];
a.push(5);
println!("{:?}", suba);
Rust rejects this because changing the vector might reallocate memory and invalidate the slice.
5. Returning a slice that does not live long enough
A slice borrows from existing data. It cannot outlive the vector it came from.
This is why Rust tracks lifetimes and prevents invalid references.
Comparisons
| Concept | Type | Owns data? | Copies data? | Can panic? | Typical use |
|---|---|---|---|---|---|
&a[0..2] | &[T] | No | No | Yes, if range is invalid | Fast borrowed view |
a[0..2].to_vec() | Vec<T> | Yes | Yes | Yes, if range is invalid | Need an independent collection |
a.get(0..2) | Option<&[T]> | No | No | No | Safe range access |
Slice vs new vector
let a = vec![1, 2, 3, 4];
let borrowed = &a[0..2];
let owned = a[0..2].to_vec();
borrowedis lightweight and efficient.ownedallocates and copies.
Indexing vs get()
let a = vec![1, 2, 3, 4];
let s1 = &a[1..3];
let s2 = a.get(1..3);
&a[1..3]is simpler, but panics if invalid.a.get(1..3)is safer because it returnsOption.
Vec<T> vs slice parameter
fn print_all(values: &[i32]) {
println!("{:?}", values);
}
Using &[T] in function parameters is usually more flexible than requiring &Vec<T>.
Cheat Sheet
Quick reference
Get a slice from a vector
let v = vec![1, 2, 3, 4];
let s = &v[0..2];
Result:
[1, 2]
Range rules
start..end→ includesstart, excludesend..end→ from beginning toendstart..→ fromstartto end..→ whole slice
Examples:
&v[..2]
&v[1..]
&v[..]
Types
Vec<T>= owned, growable collection&[T]= borrowed slice
Make a new vector from a slice
let new_vec = v[0..2].to_vec();
Safe access
let maybe_slice = v.get(0..2);
- Returns
Some(&[T])if valid - Returns
Noneif invalid
Important rules
- Slicing does not copy data
- Invalid indexed ranges panic
- Slices borrow from the original vector
- You cannot mutate the vector in ways that conflict with an active slice
FAQ
How do I slice a Vec<T> in Rust?
Use range syntax with borrowing:
let part = &vec[0..2];
This returns a slice, not a new vector.
Does slicing a vector copy the data?
No. A slice is just a borrowed view into the original vector.
How do I get a new Vec<T> from part of a vector?
Use .to_vec() on the slice:
let new_vec = vec[0..2].to_vec();
What is the type of &vec[0..2]?
It is &[T], a slice reference.
Can slicing panic in Rust?
Yes. Using an invalid range like &vec[0..10] on a shorter vector will panic.
How can I slice safely without panicking?
Use .get(range):
let result = vec.get(0..2);
This returns Option<&[T]>.
Why do Rust functions often accept &[T] instead of &Vec<T>?
Because slices are more flexible. They allow callers to pass vectors, arrays, or parts of vectors.
Can I modify a vector while holding a slice to it?
Usually not in conflicting ways. Rust prevents unsafe mutations while borrowed slices exist.
Mini Project
Description
Build a small Rust program that extracts different parts of a vector and prints them. This demonstrates how slices work, how range syntax behaves, and how to safely access ranges without panicking.
Goal
Create a program that shows borrowed slices, full slices, and safe range access with .get().
Requirements
- Create a vector with at least five numbers.
- Print a slice containing the first two elements.
- Print a slice containing the middle part of the vector.
- Use
.get()to safely try an out-of-range slice. - Create a new owned vector from one slice using
.to_vec().
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.