Срезы (slice, типы [T] и &[T]) были введены в Rust для безопасного и эффективного доступа к подмножествам массивов, векторов и других последовательностей элементов. Они позволяют избежать аллокаций и повторного копирования данных, предоставляя только "вид" или окно на часть коллекции. Это отличается как от массивов, в которых размер фиксирован на этапе компиляции, так и от динамических коллекций, которые хранят указатель и длину, но владеют памятью.
При работе с массивами и векторами в языках без строгого контроля времени жизни часто возникают ошибки выхода за пределы (out of bounds), утечки памяти и использование некорректных указателей. Важно сделать так, чтобы при работе с подмножествами коллекций не происходило копирования и не терялась безопасность памяти, что особенно актуально для системного уровня.
В Rust slice это "указатель + длина" на часть данных, не владеющий содержимым. Они всегда сопровождаются lifetime, и компилятор гарантирует, что slice не переживёт оригинал (array, Vec, String). Вся работа с slice идёт через безопасные методы доступа, а любой выход за границы приводит к panic в runtime.
Пример кода:
let arr = [1, 2, 3, 4, 5]; let slice = &arr[1..4]; // [2,3,4] тип: &[i32] let mut vec = vec![10, 20, 30]; let mut_slice: &mut [i32] = &mut vec[..2]; mut_slice[0] = 99; assert_eq!(vec, [99, 20, 30]);
Ключевые особенности:
Можно ли создать slice, превышающий размер оригинального массива или вектора?
Нет. Компилятор и runtime гарантируют, что slice можно создать только в допустимых индексах исходных данных. Попытка выйти за пределы вызовет panic.
let arr = [1, 2, 3]; let s = &arr[0..4]; // panic при запуске
Являются ли slice самостоятельными владельцами памяти?
Нет. Slice только "окно" на данные, они не владеют памятью. Попытка вернуть slice из функции, если источник локален, приведет к ошибке времени компиляции.
fn give_slice() -> &[i32] { let arr = [1,2,3]; &arr[1..] } // ошибка: arr не живет достаточно долго
Чем отличаются slice от Array в Rust на уровне типов и операций?
Array имеет фиксированную длину, известную на этапе компиляции, и полностью вложен в stack. Slice может быть любой длины, динамически определяемой, и всегда хранит указатель и длину.
let a: [u32; 3] = [1,2,3]; // Array fixed-length let s: &[u32] = &a[..]; // Slice любого размера
Программист вернул slice из функции, где был создан локальный массив. После выхода функция оригинал удалился, а slice стал "висячим" указателем. Это вызвало баг и даже аварийное завершение.
Плюсы:
Минусы:
Slice всегда создаётся по ссылке на внешние данные, владелец данных и slice живут одинаково долго. Компилятор гарантирует тесную связь времени жизни между slice и источником.
Плюсы:
Минусы: