---
title: "18.2 Vec: creating and updating"
description: "Creating vectors with Vec::new and the vec! macro, pushing and popping, and the crucial difference between get (returns Option) and [] indexing (panics out of bounds)."
url: "https://learnrust.net/chapter-18/vec-creating-and-updating/"
last_updated: "2026-06-13"
---

# 18.2 Vec: creating and updating

`Vec<T>` is the collection you'll use most: a growable list of values of one type. This lesson covers making one, adding and removing elements, and reading them, with special attention to the two ways of accessing an element, because they differ in a way that's pure Rust safety philosophy.

## Creating a vector

Two common ways to make a `Vec`. `Vec::new()` makes an empty one (an associated function, lesson [10.6](https://learnrust.net/chapter-10/associated-functions-and-constructors/)), and the `vec!` macro makes one with initial values:

```rust
fn main() {
    let mut empty: Vec<i32> = Vec::new();
    let numbers = vec![1, 2, 3];

    println!("{numbers:?}");
    empty.push(10);
    println!("{empty:?}");
}
```

```
[1, 2, 3]
[10]
```

`vec![1, 2, 3]` is the everyday way to write a vector literal; the `!` marks it a macro (like `println!`). With `Vec::new()`, the compiler often can't tell what `T` is until you push something, so you either annotate (`Vec<i32>`) or let the first `push` decide. `numbers` is a `Vec<i32>` inferred from the literal. A `Vec` prints with `{:?}` (Debug, lesson [10.7](https://learnrust.net/chapter-10/deriving-debug/)) as a bracketed list.

## Adding and removing

A `Vec` must be `mut` to change (lesson [1.4](https://learnrust.net/chapter-1/variables-mutability-initialization/)). `push` adds to the end, `pop` removes from the end and returns it:

```rust
fn main() {
    let mut stack = vec![1, 2, 3];
    stack.push(4);
    println!("{stack:?}");        // [1, 2, 3, 4]

    let last = stack.pop();
    println!("{last:?}");         // Some(4)
    println!("{stack:?}");        // [1, 2, 3]
}
```

```
[1, 2, 3, 4]
Some(4)
[1, 2, 3]
```

`pop` returns `Option<T>`, not `T`, because the vector might be empty, there might be nothing to pop. This is `Option` (lesson [11.3](https://learnrust.net/chapter-11/option/)) doing exactly its job: the type forces you to handle the empty case. That's also why `while let Some(x) = stack.pop()` (lesson [11.6](https://learnrust.net/chapter-11/if-let-while-let-let-else/)) drains a vector so cleanly: it loops until `pop` returns `None`.

## Reading elements: the two ways

Here's the lesson's centerpiece. There are two ways to read element `i`, and they handle the out-of-bounds case differently, on purpose.

**Indexing with `[]`** gives you the element directly, and *panics* if the index is out of bounds:

```rust
fn main() {
    let v = vec![10, 20, 30];
    println!("{}", v[1]);     // 20
    println!("{}", v[10]);    // panics
}
```

```
20

thread 'main' panicked at src/main.rs:4:20:
index out of bounds: the len is 3 but the index is 10
```

**`get(i)`** returns an `Option<&T>`: `Some(&element)` if the index is valid, `None` if it's out of bounds, no panic:

```rust
fn main() {
    let v = vec![10, 20, 30];
    println!("{:?}", v.get(1));     // Some(20)
    println!("{:?}", v.get(10));    // None
}
```

```
Some(20)
None
```

The difference is a choice you make per access. Use `[]` when an out-of-bounds index would be a *bug*, a broken assumption that should crash loudly (lesson [12.5](https://learnrust.net/chapter-12/when-to-panic/)'s "panic on contract violations"). Use `get` when the index might *legitimately* be out of range, the user supplied it, you're probing, so absence is an expected outcome to handle, not a crash. This is the exact panic-versus-`Result` judgment from chapter 12, applied to indexing, and it's why reading the docs (lesson [13.8](https://learnrust.net/chapter-13/reading-documentation/)) for a method's return type matters: `get` returning `Option` is the signature telling you it handles the missing case.

> **Key insight**
>
> `v[i]` panics out of bounds; `v.get(i)` returns `Option`. Both are bounds-checked, Rust never reads past the end silently (the C and C++ buffer-overflow bug class is gone, lesson [1.7](https://learnrust.net/chapter-1/reading-compiler-errors/)). The choice is only about *how* an out-of-range access is reported: a panic (it's a bug) or a `None` (it's expected). Reach for `[]` when the index is known-good and an error there means broken code; reach for `get` when out-of-range is a possibility you want to handle gracefully.

## Vectors are generic and homogeneous

A `Vec<T>` holds elements all of the same type `T` (it's generic, lesson [15.3](https://learnrust.net/chapter-15/generic-structs-and-enums/)). You can't mix an `i32` and a `String` in one `Vec`, just as a struct field has one type. When you genuinely need a sequence of mixed types, the tool is an enum whose variants cover the cases (a `Vec<Shape>`, chapter 11) or trait objects (a `Vec<Box<dyn Trait>>`, lesson [16.9](https://learnrust.net/chapter-16/trait-objects/)). The homogeneity isn't a limitation so much as the thing that makes `Vec` fast and its element type known.

## Quiz time

**Question #1**

What's the difference between `v[i]` and `v.get(i)` when `i` is out of bounds?

<details class="solution">
<summary>Show solution</summary>

`v[i]` panics ("index out of bounds"). `v.get(i)` returns `None` (its return type is `Option<&T>`), no panic. Use `[]` when an out-of-range index would be a bug that should crash; use `get` when out-of-range is a legitimate possibility you want to handle. Both are bounds-checked; Rust never reads past the end silently.

</details>

**Question #2**

Why does `vec.pop()` return `Option<T>` rather than `T`?

<details class="solution">
<summary>Show solution</summary>

Because the vector might be empty, in which case there's nothing to pop. Returning `Option<T>` (`Some(value)` or `None`) forces the caller to handle the empty case (lesson [11.3](https://learnrust.net/chapter-11/option/)) instead of assuming there's always an element. It's also what makes `while let Some(x) = vec.pop()` drain a vector cleanly.

</details>

**Question #3**

You're reading an index that a user typed in, which could easily be out of range. Which access method should you use, and why?

<details class="solution">
<summary>Show solution</summary>

`get`, which returns `Option`. User-supplied indices are untrusted and out-of-range is an expected outcome, not a bug, so you want to handle the `None` case gracefully (print an error, ask again) rather than panic. This is chapter 12's "validate untrusted input, return a recoverable result" applied to indexing. Reserve `[]` for indices you know are valid.

</details>

You can build and read a vector. The next lesson iterates over one, with `for`, and meets a borrow-checker rule that catches a classic bug: you can't modify a vector while you're iterating over it.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
