Как получить срез (slice) из Vec<T> в Rust?
Что такое срез (slice) в Rust?
Срез — это представление (view) на непрерывную последовательность элементов. Он не владеет данными, а лишь ссылается на них. Срез всегда имеет тип &[T] (неизменяемый) или &mut [T] (изменяемый). Срез — это fat pointer, который хранит указатель на первый элемент и длину.
Основные способы получения среза из Vec
Есть три основных способа, и они эквивалентны:
let vec = vec![10, 20, 30, 40, 50];
// Способ 1: Оператор взятия индекса с диапазоном
let slice1: &[i32] = &vec[1..4]; // [20, 30, 40]
// Способ 2: Метод as_slice()
let slice2: &[i32] = vec.as_slice(); // весь вектор
// Способ 3: Диапазон от начала до конца
let slice3: &[i32] = &vec[..]; // весь вектор
Почему нужен амперсанд (&)?
Выражение vec[0..3] — это синтаксис индексации, который реализуется трейтом Index. Для Vec оператор vec[i] возвращает ссылку на элемент: &T. А vec[0..3] возвращает значение типа &[T] — то есть уже ссылку. Но синтаксис Rust требует явного заимствования, поэтому вы пишете &vec[0..3]. На самом деле это два действия: сначала vec[0..3] создаёт временный срез, а затем & берёт на него ссылку. На практике компилятор оптимизирует это, и вы можете считать, что &vec[a..b] — единый идиоматичный способ.
Изменяемый срез (&mut [T])
Чтобы получить изменяемый срез, используйте &mut vec[..] или метод as_mut_slice():
let mut vec = vec![1, 2, 3, 4, 5];
// Изменяемый срез на первые три элемента
let slice: &mut [i32] = &mut vec[0..3];
slice[0] = 100; // меняем первый элемент
println!("{:?}", vec); // [100, 2, 3, 4, 5]
Важно: Пока существует изменяемый срез, исходный вектор нельзя использовать (правила заимствования).
Безопасность и проверка границ
Если вы выйдете за границы, программа запаникует (panic):
let vec = vec![1, 2, 3];
let bad = &vec[0..10]; // panic: range end index 10 out of range for slice of length 3
Чтобы избежать паники, используйте метод get(), который возвращает Option<&[T]>:
let vec = vec![1, 2, 3];
let safe_slice: Option<&[i32]> = vec.get(0..10);
match safe_slice {
Some(s) => println!("{:?}", s),
None => println!("Индекс вне диапазона"),
}
Практические примеры
fn main() {
let data = vec![10, 20, 30, 40, 50];
// Передача среза в функцию
print_first_two(&data[..2]);
// Работа с подмассивом
let middle = &data[1..4]; // [20, 30, 40]
println!("Сумма: {}", sum_slice(middle));
// Срез от индекса до конца
let tail = &data[3..]; // [40, 50]
// Пустой срез (если start == end)
let empty: &[i32] = &data[2..2];
}
fn print_first_two(s: &[i32]) {
if s.len() >= 2 {
println!("{} {}", s[0], s[1]);
}
}
fn sum_slice(s: &[i32]) -> i32 {
s.iter().sum()
}
Ключевые правила
- Срез не владеет данными — он просто ссылается на часть вектора
- Нельзя взять срез, если вектор заморожен (есть другие заимствования)
- Для изменяемого среза нужно, чтобы и вектор был
mut - Используйте
get()когда не уверены в границах - Срезы работают с любыми типами, реализующими
IndexиIndexMut
В Rust срезы — это мощный и безопасный инструмент. Они позволяют работать с частями коллекций без копирования данных и с полной проверкой типов во время компиляции.
<<Альтернативный подход: использование итераторов
Иногда вам не нужен именно срез — достаточно итератора. Если ваша функция может принимать итератор, это часто бывает гибче и безопаснее:
fn process_items(items: impl Iterator<Item = &u32>) {
for item in items {
println!("{}", item);
}
}
fn main() {
let vec = vec![10, 20, 30, 40, 50];
// Передаём итератор на часть вектора
process_items(vec[1..4].iter());
// Можно комбинировать с другими итераторами
let sum: u32 = vec.iter()
.skip(1)
.take(3)
.sum();
println!("Сумма: {}", sum);
}
Когда использовать срез, а когда итератор?
- Срез — когда нужен доступ по индексу (
s[i]) или передача в функцию, которая требует&[T] - Итератор — когда нужно последовательно обработать элементы, возможно с фильтрацией или трансформацией
Также помните, что срез — это просто ссылка, поэтому он не требует дополнительных аллокаций. Итераторы тоже zero-cost в Rust.
Ещё один полезный метод — split_at() для разделения среза на две части без паники:
let vec = vec![1, 2, 3, 4, 5];
let (left, right) = vec.split_at(3);
// left = [1, 2, 3], right = [4, 5]
Этот метод проверяет границы и возвращает кортеж из двух срезов. Он безопаснее ручного создания диапазонов.