# Как работает Vec в Rust: устройство, методы, оптимизация

> Полное руководство по Vec в Rust: устройство, методы, частые ошибки и оптимизация. Всё это — в форме космического путешествия, где капитан учит робота RUST-Y обращаться с динамическими массивами без паники и перерасхода памяти.

- **URL:** https://eagle2000.space/rust/rust-vec/
- **Date:** 2025-09-01

- **Tags:** rust, dev

---## Вступление

Иногда нам нужен список который не зажат в рамки фиксированного размера. В Rust для этого имеется `Vec<T> `динамический контейнер, который растёт и сжимается по мере необходимости.

Чтобы сделать знакомство с ним более запоминающимся, мы отправимся в путешествие с увлекательным сюжетом.

Главное действующее лицо - капитан космического корабля _«Vectoria»_. Его задача - подготовить корабль к миссии: собрать экипаж, загрузить оборудование, очистить и обслужить системы и наконец отправиться в полёт.

Вместе с капитаном будет робот-помощник RUST-Y или просто Расти. Он умеет писать программы на Rust, но часто совершает типичные ошибки новичка. Капитану придется направлять его и показывать правильные решения.


{{< alert "comment" >}}
А ещё в конце статьи вас ждёт испытание: мини-квиз, который покажет насколько хорошо вы справились с миссией и разобрались с Vec.
{{< /alert >}}

Готовы? Тогда начнём с самого простого шага: списка экипажа.

## Часть 1. Знакомство с Vec — первые шаги

Вас вызывают в штаб. Перед стартом новой космической миссии вам поручают задание - предоставить список экипажа. Пока известно только четыре члена команды, ещё двое прибудут позже.

Вы даете задание Расти — составь список экипажа. Ваша задача - научить его вести списки правильно, ведь от этого зависит порядок на борту и успех всей миссии.


### 💻 Попытка RUST-Y — ошибка новичка


#### Шаг 1

```rust
// ?compile_fail
fn main() {
    // Робот записывает имена по отдельности
    let crew1 = "Капитан Нова";
    let crew2 = "Инженер Спаркс";
    let crew3 = "Доктор Люмен";
    let crew4 = "Биолог Рост";

    // Он решает: "создам массив"
    let crew = [crew1, crew2, crew3, crew4];

    // Но сразу лезет к пятому элементу...
    let crew5 = crew[4];

    println!("Пятый член экипажа: {crew5}");
}
```

**Результат: программа упадёт с паникой.**
- Почему? Потому что массив в Rust имеет фиксированный размер — здесь ровно 4 элемента, а доступ к индексу [4] недопустим.
- 🔹 Паника — это механизм аварийного завершения, когда нарушено условие безопасности (например, выход за границы памяти).


#### Шаг 2

```rust
fn main() {
    // Список экипажа в гибкой коллекции
    let crew = vec!["Капитан Нова", "Инженер Спаркс", "Доктор Люмен", "Биолог Рост"];

    println!("Экипаж: {:?}", crew);
    println!("Длина: {}", crew.len());
    println!("Вместимость: {}", crew.capacity());
}
```

**Исправление капитана — Vec**
- Капитан объясняет:
Расти, массив — это как тетрадь на 4 страницы. Если в экипаже будет больше людей, страниц уже не хватит.
- Нам нужен гибкий список, который умеет расти. В Rust для этого есть Vec.
- Под капотом он хранит три числа:
- 1. указатель (`ptr`) на блок памяти, 
- 2. длину (`len`) — сколько элементов реально в списке,
- 3. вместимость (`capacity`) — сколько ещё можно добавить без расширения памяти.


Запустите код в шаге 2 и вы увидите примерно такой вывод:

```
Экипаж: ["Капитан Нова", "Инженер Спаркс", "Доктор Люмен", "Биолог Рост"]
Длина: 4
Вместимость: 4
```

> Представьте, что у вас есть грузовой отсек корабля.
> - В нём 4 контейнера — это len.
> - Отсек рассчитан максимум на 4 контейнера — это capacity.
> - Если вы добавите ещё один контейнер, корабль "расширит отсек" (реаллокация): выделит новый, более просторный отсек, перенесёт туда все контейнеры и продолжит работу.


### 💻 Попробую показать это в коде:


```rust
fn main() {
    let mut crew = Vec::new();

    for i in 1..=5 {
        crew.push(format!("Член экипажа #{i}"));
        println!(
            "Добавлен {i}-й: len={}, capacity={}",
            crew.len(),
            crew.capacity()
        );
    }
}
```

**Запустите код и вы увидите, что capacity растёт скачками (примерно в 2 раза).**
- Это нужно, чтобы `push` в среднем был очень быстрым (`O(1)`).


### Описание методов и конструкций

- `push`
Добавляет элемент в конец вектора. Если вектора не хватает `(len == capacity)`, он автоматически расширяет память.

- `len`
Возвращает текущее количество элементов в `Vec`.

- `capacity`
Показывает, сколько элементов можно ещё добавить без перераспределения памяти.

- `1..=5`
Это **диапазон** (range). В Rust `..= `означает "включительно", то есть от 1 до 5 включительно.
Если бы мы написали `1..5`, то получили бы числа 1, 2, 3, 4.

### Несколько способов создать Vec

В Rust есть несколько способов создать динамический вектор.

#### 1. Пустой вектор

Когда мы пока не знаем, какие элементы будут:

```rust
let mut crew: Vec<String> = Vec::new();
```

Здесь мы явно указали тип `(Vec<String>)`. Без него компилятор не поймёт, что именно мы хотим хранить.

#### 2. Макрос vec![]

Самый удобный способ, если сразу известны элементы:

```rust
let crew = vec!["Алиса", "Борис", "Саяна", "Дмитрий"];
```

#### 3. С фиксированным числом одинаковых значений

Макрос `vec!` можно использовать и так:

```rust
let crew = vec!["Неизвестный"; 3];
```

Получим вектор ["Неизвестный", "Неизвестный", "Неизвестный"].

#### 4. Через итераторы и коллекции

Например, можно создать последовательность чисел и собрать её в вектор:

```rust
let numbers: Vec<i32> = (1..=5).collect();
println!("{:?}", numbers); // [1, 2, 3, 4, 5]
```

#### 5. С заранее известной ёмкостью

Если мы знаем, что скоро придёт много элементов, выгодно сразу зарезервировать память:

```rust
let mut crew: Vec<String> = Vec::with_capacity(10);
```

Это создаст пустой вектор, у которого `len = 0`, но `capacity = 10`.

{{< alert "lightbulb" >}}
На практике чаще всего используют vec![], а остальные способы нужны для оптимизаций или специфических случаев.
{{< /alert >}}

## Часть 2. Проверка экипажа перед тренировкой (паника и безопасный доступ)

Перед первой тренировкой капитан просит RUST-Y вызвать члена экипажа по индексу из списка.
Но если робот ошибётся с номером, программа завершится аварийно. Это важный момент: такие сбои недопустимы.


### 💻 Попытка RUST-Y - ошибка новичка


#### Шаг 1

```rust
// ?should_panic
fn main() {
    let crew = vec![
        "Капитан Нова",
        "Инженер Спаркс",
        "Доктор Люмен",
        "Биолог Рост",
        "Пилот Вега",
        "Связист Хекс",
    ];

    // Попытка вызвать участника с индексом 99
    let who = crew[99]; // ❌ паника
    println!("На тренировку выходит: {}", who);
}
```

**Результат: программа упадёт с паникой.**
- Что произошло? В Rust доступ по индексу (`crew[99]`) проверяется на корректность. Если индекс вне диапазона, программа завершается **паникой**..
- В отличие от C/C++, где можно случайно прочитать "левые байты" или сломать память Rust останавливает программу и не даёт ей работать некорректно.


#### Шаг 2

```rust
fn main() {
    let crew = vec![
        "Капитан Нова",
        "Инженер Спаркс",
        "Доктор Люмен",
        "Биолог Рост",
        "Пилот Вега",
        "Связист Хекс",
    ];

    match crew.get(99) {
        Some(name) => println!("На тренировку выходит: {}", name),
        None => println!("Ошибка: участника с таким номером не существует."),
    }
}
```

**Исправление капитана — безопасный доступ**
- Разбор:
- [] возвращает ссылку &T, но вызывает панику, если индекс неверный.
- `.get(index)` возвращает `Option<&T>`: `Some(&T)` — элемент найден, `None` — индекс за пределами.


{{< alert "lightbulb" >}}
Паника в Rust — это защита и повод проверить ваш код.

Если индекс может быть неверным, используйте `.get()`.
{{< /alert >}}

### Немного про Option

В статье про функциональный Rust мы уже встречались с `Option` как с типом, который позволяет явно сообщить: "значение может быть, а может не быть" такой аналог null-safety из других языков.

В примере выше `Option` используется так:
- `Some(value)` — элемент в векторе есть.
- `None` — элемента нет (например, индекс за пределами).

Это хорошее решение языка: вместо "магических значений" или "падений программы" мы получаем явную модель отсутствия данных.

## Часть 3. Личные коды экипажа (итерация по вектору и изменение элементов)

Перед полетом необходимо пройти инструктаж и капитан требует, чтобы каждый член экипажа получил личный код доступа в формате: `R-XXX Имя`

Например:
- R-001 Капитан Нова
- R-002 Инженер Спаркс

Робот RUST-Y пробует сделать это с помощью цикла и сразу натыкается на ошибку компилятора.


### 💻 Попытка RUST-Y


#### Шаг 1

```rust
// ?compile_fail
fn main() {
    let mut names = vec![
        String::from("Капитан Нова"),
        String::from("Инженер Спаркс"),
        String::from("Доктор Люмен"),
        String::from("Биолог Рост"),
        String::from("Пилот Вега"),
        String::from("Связист Хекс"),
    ];

    // ❌ Ошибочный подход: iter() даёт неизменяемые ссылки (&String)
    for (i, name) in names.iter().enumerate() {
        *name = format!("R-{:03} {}", i + 1, name);
        println!("Подготовка кода для: {name} (#{})", i + 1);
    }
}
```

**Почему не компилируется**
- `names.iter()` возвращает неизменяемые ссылки `&String`.
- Изменять значения по таким ссылкам запрещено.
- Чтобы изменять элементы вектора, нужны изменяемые ссылки `&mut String.`


#### Шаг 2

```rust
fn main() {

    let mut names = vec![
        String::from("Капитан Нова"),
        String::from("Инженер Спаркс"),
        String::from("Доктор Люмен"),
        String::from("Биолог Рост"),
        String::from("Пилот Вега"),
        String::from("Связист Хекс"),
    ];
for (i, name) in names.iter_mut().enumerate() {
        *name = format!("R-{:03} {}", i + 1, *name);
    }

    println!("Коды присвоены:\n{:#?}", names);
}
```

**Исправление капитана — iter_mut()**
- Капитан объясняет:
Теперь тип name стал &mut String и значение по ссылке можно изменять


### Разбор новых понятий

#### iter() vs iter_mut()
- `iter(&self)` ->` Iter<'_, T>` — даёт `&T` (чтение).
- `iter_mut(&mut self)` -> `IterMut<'_, T>` — даёт `&mut T` (изменение).

> ⚠️ Пока выполняется iter_mut(), нельзя иметь другие заимствования — это правило единственной изменяемой ссылки в Rust.

#### enumerate()
- Оборачивает итератор и добавляет индекс.
- Индексы начинаются с 0, поэтому для "человекопонятной нумерации" используем `i + 1`.


#### Изменение элемента через *name
- В цикле `name` имеет тип `&mut String`.
- Чтобы присвоить новое значение, используем разыменование

```rust
*name = format!("R-{:03} {}", i + 1, *name);
```

#### format!
- Макрос для сборки строк по шаблону.
- Возвращает новый `String`.
- Экономия на ручных конкатенациях (суммирования строк).


## Часть 4. Массовая загрузка корабля и проверка приборов

Подготовка к миссии выходит на новый уровень.

Корабль _Vectoria_ должен принять на борт сотни контейнеров и провести проверку бортовых приборов.

Робот Расти пытается записать данные как есть, но работа получается "рваной" — список растёт с перебоями и задержками.

Капитан объясняет, как избежать лишних перераспределений памяти при массовой загрузке.



### 💻 Попытка RUST-Y — загрузка


#### Шаг 1

```rust
use std::time::Instant;

fn main() {
    // Симулируем поток данных от датчиков
    let incoming: Vec<String> = (1..=20_000)
        .map(|i| format!("Прибор #{i:05}"))
        .collect();

    let start = Instant::now();
    let mut manifest: Vec<String> = Vec::new();

    let mut last_cap = manifest.capacity();
    let mut reallocs = 0;

    for item in incoming {
        manifest.push(item); // добавляем по одному
        if manifest.capacity() != last_cap {
            reallocs += 1;
            last_cap = manifest.capacity();
        }
    }

    println!(
        "Загрузка завершена: len={}, capacity={}, reallocs={}, elapsed={:?}",
        manifest.len(),
        manifest.capacity(),
        reallocs,
        start.elapsed()
    );
}
```

**Что происходит:**
- `Vec` начинает с минимальной ёмкости.
- При каждом переполнении выделяется новый блок памяти → копирование старых данных.
- На больших объёмах видно множество реаллокаций.


#### Шаг 2

```rust
use std::time::Instant;

fn main() {
    let incoming: Vec<String> = (1..=20_000)
        .map(|i| format!("Прибор #{i:05}"))
        .collect();
    let mut reallocs = 0;
    let start = Instant::now();

// Предвыделяем память под все элементы заранее
    let mut manifest: Vec<String> = Vec::with_capacity(incoming.len());

    // Массовая вставка
    manifest.extend(incoming);
    reallocs += 1;

    println!(
        "Загрузка завершена: len={}, capacity={}, reallocs={}, elapsed={:?}",
        manifest.len(),
        manifest.capacity(),
        reallocs,
        start.elapsed()
    );
}
```

**Исправление капитана — предвыделение памяти)**
- Капитан объясняет, что теперь:
- нет скачков ёмкости,
- данные загружаются быстрее,
- память выделяется ровно один раз.


### Разбор кода для 2 шага

- `push` добавляет по одному элементу.
- `extend` добавляет коллекцию или итератор целиком.
- `append(&mut other)` переносит данные из другого вектора без копирования (после этого other опустошается).
- `with_capacity(n)` сразу резервирует место под n элементов.
- `reserve(n) / reserve_exact(n)` — динамическое выделение при работе порциями.

{{< alert "lightbulb" >}}

**Аналогия с роботом и складом**

Представьте складской отсек:

- RUST-Y тащит ящики один за одним, каждый раз переставляя стены отсека когда не хватает объема помещения (реаллокации).
- Капитан заранее строит отсек нужного размера — и загрузка идёт без перебоев.
{{< /alert >}}


## Часть 5. Буфер телеметрии — только последние данные

Корабль _Vectoria_ готовится к полёту. Приборы начинают выдавать поток телеметрии: десятки и сотни значений в секунду.

Хранить всё подряд — невозможно: память ограничена.

Задача: держать в буфере только последние N записей, а старые сбрасывать.

Расти пишет свой вариант и, как обычно, попадает в ловушку новичка.


### 💻 Попытка RUST-Y — неограниченный рост


#### Шаг 1

```rust
fn main() {
    let mut telemetry: Vec<i32> = Vec::new();

    // Симулируем поток данных
    for i in 0..200 {
        telemetry.push(i);
    }

    println!("Собрано {} значений", telemetry.len());
}
```

**Проблема:**
- буфер растёт бесконечно,
- через некоторое время память будет исчерпана.


#### Шаг 2

```rust
fn main() {
    let mut telemetry: Vec<i32> = Vec::new();
    let max_len = 100;

    for i in 0..200 {
        telemetry.push(i);

        // если размер превысил лимит, убираем старые записи
        if telemetry.len() > max_len {
            telemetry.remove(0); // ❌ неэффективно, сдвигает элементы
        }
    }

    println!("Буфер телеметрии: {:?}", telemetry);
}
```

**Исправление капитана — ограниченный буфер**
- Работает, но `remove(0)` сдвигает все элементы - на больших объёмах это дорого.


#### Шаг 3

```rust
fn main() {
    let mut telemetry: Vec<i32> = Vec::new();
    let max_len = 100;

    for i in 0..200 {
        telemetry.push(i);

        if telemetry.len() > max_len {
            let overflow = telemetry.len() - max_len;
            telemetry.drain(0..overflow);
        }
    }

    println!("Буфер телеметрии: {:?}", telemetry);
}
```

**Ещё лучше — `drain`**
- `drain(0..overflow)` удаляет сразу несколько элементов с начала.
- В буфере всегда остаются последние `max_len` записей.


#### Шаг 4

```rust
fn main() {
    let mut telemetry: Vec<i32> = Vec::new();
    let max_len = 100;

    for i in 0..200 {
        telemetry.push(i);
    }
let last100: Vec<i32> = telemetry.iter().rev().take(100).cloned().collect();


    println!("Буфер телеметрии: {:?}", last100);
}
```

**Альтернативное решение — срез последних элементов**
- Иногда проще просто брать «хвост» вектора:


## Часть 6. Итоги и старт миссии 🚀

Итак все системы корабля _Vectoria_ проверены.

- Экипаж собран и получил личные коды.
- Приборы загружены и протестированы.
- Буфер телеметрии настроен чтобы хранить только актуальные данные.

Робот RUST-Y научился многому: он уже не делает типичных ошибок новичка и готов сопровождать миссию в полёте.

Капитан даёт сигнал: **"К старту готовы!"**

Проверьте себя: пройдите финальный квиз ниже. 

Время прохождения ограничено, но дано с запасом. Вопросы каждый раз перемешиваются и вы можете проходить квиз несколько раз.

> Спасибо, что прочитали статью про векторы в Rust до конца, надеюсь что смог объяснить как их правильно готовить! :D


{{< typeit
      speed=50
>}}
Продолжение следует и удачи, капитан!
{{< /typeit >}}

🛰️


### 📝 Rust Vec


**1. Что произойдёт при выполнении кода?**

- [ ] Напечатает пустую строку
- [ ] Вернёт None
- [ ] Программа упадёт с паникой
- [ ] Автоматически расширит вектор

**2. Как создать пустой вектор строк?**

- [ ] Vec::new()
- [ ] vec!()
- [ ] Vec::<String>::new()
- [ ] Все варианты верны

**3. Что хранит Vec под капотом?**

- [ ] Только список элементов
- [ ] Указатель на данные, длину и вместимость
- [ ] Только длину и данные
- [ ] Только вместимость

**4. Чем массив [T; N] отличается от Vec<T>?**

- [ ] Массив фиксированного размера, Vec — динамический
- [ ] Vec не может хранить строки
- [ ] Массивы медленнее чем Vec
- [ ] Vec работает только с числами

**5. Что вернёт следующий код?**

- [ ] Панику
- [ ] Some("")
- [ ] None
- [ ] Компилятор выдаст ошибку

**6. Чем отличается crew[5] от crew.get(5)?**

- [ ] Разницы нет, оба возвращают ссылку
- [ ] crew[5] может вызвать панику, crew.get(5) возвращает Option
- [ ] crew[5] всегда безопасен
- [ ] crew.get(5) расширяет вектор

**7. Почему этот код не компилируется?**

- [ ] iter() не поддерживает строки
- [ ] iter() возвращает &String, а не &mut String
- [ ] enumerate() нельзя использовать вместе с iter()
- [ ] Вектор по умолчанию неизменяемый

**8. Какой тип имеет переменная name в этом цикле?**

- [ ] String
- [ ] &String
- [ ] &mut String
- [ ] Option<&mut String>

**9. Что делает метод enumerate()?**

- [ ] Превращает вектор в массив
- [ ] Добавляет индекс к каждому элементу при обходе
- [ ] Создаёт новый Vec<(usize, T)>
- [ ] Изменяет элементы по месту

**10. Чем отличается push от extend?**

- [ ] push добавляет один элемент, extend — целую коллекцию
- [ ] Разницы нет, это синонимы
- [ ] extend всегда копирует данные, а push — нет
- [ ] push требует &mut Vec, а extend — нет

**11. Что произойдёт в этом коде?**

- [ ] [1, 2] [3, 4]
- [ ] [1, 2, 3, 4] []
- [ ] [1, 2, 3, 4] [3, 4]
- [ ] Ошибка компиляции

**12. Что делает метод with_capacity(n)?**

- [ ] Создаёт вектор длиной n
- [ ] Резервирует место для n элементов, но оставляет длину равной 0
- [ ] Создаёт массив фиксированного размера
- [ ] Всегда удваивает вместимость

**13. Почему загрузка в Vec через push может работать медленнее?**

- [ ] Каждый push требует вызова format!
- [ ] При переполнении Vec приходится выделять новый блок памяти и копировать элементы
- [ ] push всегда компилируется медленнее чем extend
- [ ] Vec не поддерживает большие коллекции

**14. Что делает метод truncate(n)?**

- [ ] Убирает все элементы
- [ ] Оставляет только первые n элементов
- [ ] Удаляет элементы с начала
- [ ] Всегда очищает вектор

**15. Чем remove(0) хуже, чем drain(0..1) для больших буферов?**

- [ ] remove(0) не работает с векторами
- [ ] remove(0) сдвигает все элементы, drain может убрать сразу диапазон
- [ ] Разницы нет
- [ ] drain всегда медленнее

**16. Как оставить в векторе только последние N элементов при росте?**

- [ ] Через truncate(N)
- [ ] Через drain(0..len-N)
- [ ] Через remove(len-N)
- [ ] Через get(N)

**17. Почему буфер с push без удаления не подходит для телеметрии?**

- [ ] push не работает для чисел
- [ ] Буфер будет расти бесконечно и съест всю память
- [ ] push автоматически удаляет старые значения
- [ ] Vec ограничен максимум 100 элементами

