ПрограммированиеBackend разработчик

Как работает механизм slice'ов в Rust, какие типы slice существуют, как обеспечивается безопасность и что произойдет при попытке выйти за пределы slice?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Slice (срез) в Rust — это динамическое представление части коллекции, у которой элементы располагаются в памяти последовательно. Типичный пример среза — &[T] или &mut [T]. Slice-ы не владеют данными, а ссылаются на внешний буфер. У всех срезов есть длина, хранимая вместе с ссылкой.

Основные типы:

  • &[T] — неизменяемый срез
  • &mut [T] — изменяемый срез
  • Для строк: &str (фактически — срез байтов, всегда корректный UTF-8)

Безопасность slice обеспечивается на уровне компилятора и рантайма: любые попытки обратиться к элементу за пределами среза вызовут панику (panic) на этапе выполнения, никакой непримиряемой ошибки в памяти как в C/C++ не произойдет.

Пример:

let v = vec![1, 2, 3, 4, 5]; let s: &[i32] = &v[1..4]; // срез, ссылается на элементы 2, 3, 4 println!("{:?}", s); // [2, 3, 4] // s[3]; // panic! (выход за границы)

Вопрос с подвохом

Могут ли slice-ы быть пустыми? Чем отличается пустой slice от None?

Ответ: Да, slice могут быть пустыми (&[]), это означает ссылку на часть данных с нулевой длиной, но не является аналогом None. Пустой slice безопасен к использованию, а Option<&[T]> служит для различения "есть ли срез вообще".

Пример:

let s: &[i32] = &[]; assert!(s.is_empty()); // Option<&[i32]> используется, если среза может не быть совсем.

Примеры реальных ошибок из-за незнания тонкостей темы


История

В крупном сервисе логов программист взял slice по диапазону, рассчитанному динамически от пользовательских данных. При ошибочном расчёте slice (например, при start > end или если end > len), код сработал на тестах, но на продакшене вызвал "panic" и аварийно остановил процесс в пике нагрузок.


История

Во внутренней библиотеке concurremt-хэширования использовали &mut [T] и параллельно несколько потоков брали разные срезы одного массива. Один поток изменял slice, а другой на ту же память брал другой slice. Программа компилировалась, но из-за неправильного деления могли возникнуть UB через небезопасный код (использование unsafe), если slices всё же пересекались.


История

В системном парсере сетевых пакетов создавался срез небезопасным образом (raw pointer и from_raw_parts). Разработчик забыл проверить корректность длины входного пакета. В результате попытка чтения за пределами привела к падению приложения и уязвимости (potential OOB access), которая могла быть устранена правильным применением безопасных slice-ов.