Question

How to Get a Slice of a Vec<T> in Rust

rustvector

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.”

Take Quiz

Syntax and Examples

Rust uses range syntax with indexing to create slices.

Basic syntax

let slice = &vec[start..end];
  • start is included
  • end is 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:

  1. let a = vec![10, 20, 30, 40];

    • A vector is created with four elements.
    • Index positions are:
      • 0 -> 10
      • 1 -> 20
      • 2 -> 30
      • 3 -> 40
  2. let part = &a[1..3];

    • Rust reads the range 1..3.
    • Start at index 1.
    • Stop before index 3.
    • That includes elements at indices 1 and 2.
    • part becomes a slice referencing [20, 30].
  3. 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.

Take Quiz

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:

  • start is included
  • end is 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

ConceptTypeOwns data?Copies data?Can panic?Typical use
&a[0..2]&[T]NoNoYes, if range is invalidFast borrowed view
a[0..2].to_vec()Vec<T>YesYesYes, if range is invalidNeed an independent collection
a.get(0..2)Option<&[T]>NoNoNoSafe 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();
  • borrowed is lightweight and efficient.
  • owned allocates 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 returns Option.

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>.

Take Quiz

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 → includes start, excludes end
  • ..end → from beginning to end
  • start.. → from start to 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 None if 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.

Take Quiz

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().
Take Quiz

Keep learning

Related questions

View more