Vec<T> — это динамический, growable массив, хранящий элементы в едином аллоцированном (на куче) блоке памяти. Добавление нового элемента (push) увеличивает длину, при необходимости — выделяется новая память (реаллоцируется). При push внутри происходит увеличение capacity по экспоненте, чтобы избежать постоянных реаллокаций. При удалении элемента (pop/remove) capacity не уменьшается автоматически.
Частая проблема — избыточные аллокации и realocation при постоянном добавлении.
Пример работы с предварительным выделением:
let mut v = Vec::with_capacity(1000); for i in 0..1000 { v.push(i); } assert_eq!(v.capacity(), 1000);
Вопрос: Что произойдет с capacity vec после вызова v.shrink_to_fit()? Будет ли точно равен длине?
Неправильный ответ: Да, always, after shrink_to_fit capacity == len.
Правильный ответ: Не обязательно, реализация shrink_to_fit — это "пожелание" аллокатору. Обычно стремится к минимально возможной capacity, возможны особенности в зависимости от реализации аллокатора (например, может остаться выше длины).
Пример:
let mut v = Vec::with_capacity(10); for i in 0..5 { v.push(i); } v.shrink_to_fit(); // capacity ≥ len (5), но не гарантировано, что == len
История
Разработчик многократно пушил объекты в Vec без задание capacity, что приводило к экспоненциальному росту realocation на больших объемах данных, тормозя всю обработку в "тяжелых" циклах. Оптимизация с
with_capacityуменьшила время в 10 раз.
История
Команда попыталась сэкономить память через регулярный вызов
shrink_to_fitпосле каждого pop(). В итоге возникли циклы постоянных realocate/free и деградация производительности, чуть не доведя сервис до DoS.
История
Оставили Vec в качестве поля структуры с внутренними ссылками на его элементы. После realocation (push сверх capacity) ссылки инвалидировались — возникшие баги сложно вычислялись вплоть до запуска в production.