Avatar ·

Как создать вектор нулей заданного размера в Rust?

Всем привет! Я только начал изучать Rust и столкнулся с, казалось бы, простой задачей: мне нужно создать вектор (Vec), заполненный нулями, определённого размера. В Python я бы написал `[0] * n`, в C++ — `std::vector v(n, 0)`, но в Rust я не могу найти прямого аналога. Я пробовал несколько вариантов, но они не работают или выглядят странно. Например: ```rust let size = 10; let my_vec: Vec = vec![0; size]; // Это компилируется, но правильно ли? ``` Ещё я видел в документации метод `with_capacity`, но он, кажется, просто резервирует память, не заполняя её: ```rust let mut my_vec: Vec = Vec::with_capacity(size); // my_vec.len() всё равно 0 ``` Мне нужно, чтобы вектор сразу после создания содержал ровно N нулей, чтобы я мог обращаться к элементам по индексу без паники. В моей реальной задаче я хочу создать вектор для хранения результатов вычислений — например, вектор из 1000 элементов, который я потом буду заполнять в цикле. Подскажите, как правильно и идиоматично создать вектор нулей в Rust? Есть ли разница между разными способами по производительности? Заранее спасибо! <<>>

Правильный способ: макрос vec! с повторителем

То, что вы написали — vec![0; size] — это абсолютно правильный и идиоматичный способ создать вектор из N нулей в Rust. Этот синтаксис называется «повторитель» (repeat expression) внутри макроса vec!. Он работает для любого типа, который реализует типаж Clone (а все примитивные типы вроде i32, f64, bool его реализуют).

let size = 10;
let zeros: Vec<i32> = vec![0; size];
println!("{:?}", zeros); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
println!("Длина: {}", zeros.len()); // 10

Как это работает под капотом?

Макрос vec![value; n] разворачивается в следующий код (упрощённо):

let mut vec = Vec::with_capacity(n);
vec.resize(n, value);
vec

То есть он:

  • Выделяет память под N элементов (без лишнего резерва).
  • Заполняет все N позиций копированием value с помощью Clone.
  • Устанавливает длину вектора равной N.

Это значит, что после vec![0; size] вы можете сразу обращаться к любому элементу по индексу — zeros[0], zeros[size-1] — без риска паники.

Альтернативные подходы и их особенности

Вот несколько других способов, которые могут быть полезны в разных ситуациях:

  1. Метод repeat из итераторов:
    let zeros: Vec<i32> = std::iter::repeat(0).take(size).collect();
    Этот способ более гибкий, если вам нужно не просто повторить одно значение, а, например, сгенерировать последовательность. Но для простого «вектора нулей» он избыточен и может быть чуть медленнее из-за динамической коллекции.
  2. Метод resize для существующего вектора:
    let mut zeros = Vec::with_capacity(size);
    zeros.resize(size, 0);
    Полезно, если вы сначала создаёте пустой вектор с запасом, а потом точно знаете, какой размер ему нужен.
  3. Метод new + цикл (НЕ РЕКОМЕНДУЕТСЯ):
    let mut zeros = Vec::new();
    for _ in 0..size {
        zeros.push(0);
    }
    Этот способ работает, но он менее эффективен, так как push может вызывать несколько перераспределений памяти, если не вызвать with_capacity заранее.

Производительность: что выбрать?

Для большинства случаев используйте vec![0; size] — это самый короткий, понятный и эффективный вариант. Компилятор Rust (особенно с оптимизациями) часто превращает его в вызов memset или аналогичный низкоуровневый код, что даёт производительность, близкую к C.

Если вы работаете с очень большими векторами (миллионы элементов) и хотите избежать лишней инициализации (например, вы всё равно планируете перезаписывать все элементы сразу после создания), можно использовать Vec::with_capacity(size) и заполнять его вручную. Но будьте осторожны: обращение к неинициализированным элементам через [] вызовет панику, так как длина вектора равна 0.

// Безопасный способ для отложенной инициализации:
let mut zeros = vec![0; size]; // инициализируем нулями
// ... потом быстро перезаписываем в цикле

Важное замечание про типы

Повторитель работает только для типов, реализующих Clone. Если вам нужно создать вектор из значений, которые не являются Clone (например, строк String или пользовательских структур), используйте итератор или цикл с явным созданием каждого элемента.

// Для String нужно создавать каждый элемент отдельно:
let strings: Vec<String> = (0..size).map(|_| String::new()).collect();

В итоге, для вашей задачи с вектором нулей используйте vec![0; size] — это и есть «стандартный» способ в Rust.

<<>>

Дополнение: безопасная работа с неинициализированной памятью

Если вас беспокоит производительность инициализации нулями для очень больших векторов (например, 10 миллионов элементов), и вы точно знаете, что заполните все элементы вручную, можно использовать Vec::with_capacity вместе с unsafe методом set_len. Но этот подход требует осторожности!

let size = 10_000_000;
let mut zeros: Vec<i32> = Vec::with_capacity(size);
// ПРЕДУПРЕЖДЕНИЕ: это unsafe, так как мы говорим Rust, что память инициализирована
unsafe {
    zeros.set_len(size);
}
// Теперь zeros.len() == size, но элементы содержат "мусор"!
// Нужно немедленно заполнить их:
for x in zeros.iter_mut() {
    *x = 0; // или любое другое значение
}

Этот способ может быть быстрее, если вы всё равно собираетесь перезаписывать все элементы, потому что избегает двойной записи (сначала ноль, потом ваше значение). Однако я не рекомендую его новичкам — легко ошибиться и получить undefined behavior. Для 99% случаев vec![0; size] работает отлично.

Также есть крейт arrayvec (из внешних зависимостей), который предоставляет массивы фиксированного размера на стеке с похожей функциональностью, но это уже выходит за рамки стандартной библиотеки.