Срезы Vec<T> в Rust: как это работает и зачем это нужно?
Если вы когда-нибудь писали на Rust и задавались вопросом "How do I get a slice of a Vec<T> in Rust?" — вы не одиноки. Это один из самых популярных вопросов на Stack Overflow, набравший тысячи просмотров. И неспроста: работа с векторами и их срезами — фундаментальный навык, без которого невозможно представить ни один серьёзный проект на Rust.
В этой статье мы не просто покажем, как получить срез. Мы разберём лучшие практики 2025 года, научимся избегать типичных ловушек и поймём, почему Rust вообще заставляет нас так много думать о заимствованиях. Поехали!
Что такое срез (slice) в Rust?
Прежде чем брать срез, давайте разберёмся, что это такое. Срез — это ссылка на непрерывную последовательность элементов в памяти. В Rust срез обозначается как &[T]. Это не владеющая структура данных — она просто «смотрит» на часть данных, принадлежащих кому-то другому (например, вектору или массиву).
Главное преимущество срезов — безопасность. Компилятор Rust гарантирует, что вы не выйдете за границы памяти, а система заимствования не позволит вам изменить данные, пока на них есть активные ссылки.
Почему не использовать вектор напрямую?
Вектор (Vec<T>) — это владеющая структура. Если вы передаёте его в функцию, вы либо перемещаете его (теряя доступ), либо передаёте ссылку. Срезы дают вам гибкость: вы можете передать только часть данных, не копируя их и не теряя контроль над исходным вектором.
Например, вы работаете с большим массивом данных и хотите передать его фрагмент в функцию для анализа. Срез — идеальный инструмент: он лёгкий (всего два слова: указатель и длина) и безопасный.
Основные способы получить срез Vec<T>
Давайте перейдём к практике. Есть несколько способов получить срез из вектора. Мы рассмотрим самые популярные и удобные.
1. Оператор индексирования: &vec[start..end]
Самый прямолинейный способ — использовать оператор диапазона ..:
let vec = vec![1, 2, 3, 4, 5];
let slice: &[i32] = &vec[1..4]; // срез от индекса 1 до 4 (не включая 4)
println!("{:?}", slice); // [2, 3, 4]
Этот метод использует рантайм-проверку границ. Если вы выйдете за пределы вектора, программа запаникует (panic). Для учебных примеров это нормально, но в production-коде стоит быть осторожнее.
Вы можете использовать различные формы диапазонов:
&vec[..]— весь вектор&vec[2..]— от индекса 2 до конца&vec[..3]— от начала до индекса 3 (не включая)&vec[1..=3]— от 1 до 3 включительно
2. Метод .as_slice()
Этот метод возвращает срез на весь вектор. Он безопаснее, чем &vec[..], потому что явно выражает намерение:
let vec = vec![10, 20, 30];
let slice: &[i32] = vec.as_slice();
println!("{:?}", slice); // [10, 20, 30]
Метод as_slice() особенно полезен, когда вы хотите подчеркнуть, что работаете со срезом, а не с вектором. Это делает код более читаемым и самодокументируемым.
3. Получение изменяемого среза: as_mut_slice()
Если вам нужно изменить элементы внутри среза (но не сам вектор), используйте as_mut_slice():
let mut vec = vec![1, 2, 3];
let slice: &mut [i32] = vec.as_mut_slice();
slice[0] = 100;
println!("{:?}", vec); // [100, 2, 3]
Обратите внимание: вектор должен быть объявлен как mut. Также вы не можете одновременно иметь неизменяемый и изменяемый срезы — это нарушит правила заимствования.
Как получить подмассив (sub-slice) без паники?
В production-коде паника из-за выхода за границы — это плохо. Rust предоставляет безопасные альтернативы:
Метод .get()
Возвращает Option<&[T]>:
let vec = vec![1, 2, 3, 4, 5];
let slice = vec.get(1..4);
match slice {
Some(s) => println!("{:?}", s), // [2, 3, 4]
None => println!("Индекс вне диапазона"),
}
Это идеальный выбор, когда вы не уверены в корректности индексов.
Метод .get_unchecked()
Если вы абсолютно уверены, что индексы корректны, можно использовать get_unchecked(). Он не проверяет границы и может привести к неопределённому поведению при ошибке. Используйте только в критических участках кода, где производительность важнее безопасности, и только после тщательной проверки.
let vec = vec![1, 2, 3];
// Без проверки границ — используйте с осторожностью!
let slice = unsafe { vec.get_unchecked(0..2) };
println!("{:?}", slice); // [1, 2]
Срезы и заимствования: типичные ошибки
Самая частая ошибка новичков — попытка изменить вектор, пока на него есть активный срез. Rust это запрещает:
let mut vec = vec![1, 2, 3];
let slice = &vec[..];
vec.push(4); // Ошибка! Нельзя изменять vec, пока slice заимствован
println!("{:?}", slice);
Компилятор скажет что-то вроде "cannot borrow `vec` as mutable because it is also borrowed as immutable". Это не баг, а фича: Rust защищает вас от ошибок, которые могли бы привести к висячим указателям.
Решение: либо завершите работу со срезом, прежде чем изменять вектор, либо используйте изменяемый срез (&mut [T]), если это возможно.
Лучшие практики 2025 года
Rust развивается, и вместе с ним меняются лучшие практики. Вот что стоит знать и использовать в 2025 году:
1. Используйте as_slice() для явности
Хотя &vec[..] работает, vec.as_slice() читается лучше и сразу говорит о намерении. Это особенно важно