невладеющее представление непрерывного участка данныхa non-owning view over contiguous data

Slices

Slice — невладеющий "толстый указатель" на непрерывный участок данных: указатель плюс длина, без владения самими данными. &[T] — срез любой непрерывной коллекции T; &str — срез валидных UTF-8 байтов, заимствованный аналог владеющего String.A slice is a non-owning "fat pointer" view into a contiguous sequence of elements — a pointer plus a length, with no ownership of the underlying data. &[T] is a slice of any contiguous T collection; &str is a slice of validated UTF-8 bytes, the borrowed counterpart of the owning String.

основа языкаcore of the language частый вопрос: чем &[T] отличается от &Vec<T>common question: how does &[T] differ from &Vec<T>

Что этоWhat it is

Slice — это невладеющая "толстая" ссылка на непрерывный участок данных: под капотом это пара (указатель на первый элемент, длина), без какого-либо владения самими данными (см. borrowing-references.html — slice является специализированной формой заимствования). &[T] — срез любой непрерывной коллекции T (Vec<T>, массива, другого среза). &str — специфически срез байтов, провалидированных как UTF-8 (строковый срез), заимствованный аналог владеющего типа String, концептуально ровно то же отношение, что &[T] к Vec<T>.A slice is a non-owning "fat" reference to a contiguous run of data: under the hood it's a pair (pointer to the first element, length), with no ownership of the underlying data at all (see borrowing-references.html — a slice is a specialized form of borrowing). &[T] is a slice of any contiguous T collection (a Vec<T>, an array, another slice). &str is specifically a slice of bytes validated as UTF-8 (a string slice), the borrowed counterpart of the owning String type — conceptually the exact same relationship as &[T] to Vec<T>.

let v = vec![10, 20, 30, 40];
let middle: &[i32] = &v[1..3];  // срез из двух элементов, без копирования// a two-element slice, no copying
println!("{:?}", middle);  // [20, 30]// [20, 30]

Зачем ввелиWhy it was introduced

Slices решают задачу, которая до них требовала выбора между двумя плохими вариантами:Slices solve a problem that previously forced a choice between two bad options:

01

Владение или конкретный контейнер — оба варианта плохиOwnership or a specific container — both options are bad

До slices функция, которой нужен был "какой-то непрерывный участок элементов", должна была либо забирать владение (теряя данные вызывающего или вынуждая клонирование), либо быть завязана на конкретный владеющий тип-контейнер (&Vec<T>) — что неоправданно ограничительно: такая функция не может принять простой массив или под-диапазон существующего вектора без копирования.Before slices, a function that needed "some contiguous run of elements" had to either take ownership (losing the caller's data or forcing a clone) or be tied to a specific owning container type (&Vec<T>) — needlessly restrictive, since it couldn't accept a plain array or a sub-range of an existing vector without a copy.

02

Slices отделяют "вид на данные" от конкретного владеющего контейнераSlices decouple "a view over data" from any specific owning container

Slice даёт единый тип для "непрерывного участка данных, откуда бы он ни происходил" — Vec, массив, другой slice — не заботясь о том, кто именно владеет исходными данными.A slice gives a single type for "a contiguous run of data, wherever it comes from" — a Vec, an array, another slice — without caring who actually owns the source data.

03

C++ пришёл к тому же решению на десятилетие позжеC++ arrived at the same solution roughly a decade later

C++ решил ровно ту же задачу через std::span (C++20) и std::string_view (C++17) — Rust имел эту возможность с версии 1.0, на 5-8 лет раньше.C++ solved the identical problem via std::span (C++20) and std::string_view (C++17) — Rust had this since 1.0, five to eight years earlier.

Как это работает под капотомHow it works under the hood

Функция, принимающая &[i32], работает одинаково независимо от того, вызвана ли она с &Vec<i32> (благодаря deref coercion), с обычным массивом &[i32; 5] или с другим срезом:A function accepting &[i32] works identically whether called with a &Vec<i32> (via deref coercion), a plain array &[i32; 5], or another slice:

fn first_two(s: &[i32]) -> &[i32] { &s[0..2] }

let v = vec![1, 2, 3];
let arr = [4, 5, 6];
first_two(&v);    // &Vec<i32> deref-coerces в &[i32]// &Vec<i32> deref-coerces to &[i32]
first_two(&arr);  // &[i32; 3] тоже coerces в &[i32]// &[i32; 3] also coerces to &[i32]

Для &str действует тот же принцип: &String deref-coerces в &str, а подстрока получается той же range-нотацией:The same principle applies to &str: a &String deref-coerces to &str, and a substring is obtained with the same range notation:

let owned = String::from("hello world");
let s: &str = &owned;         // &String deref-coerces в &str// &String deref-coerces to &str
let hello = &owned[0..5];    // "hello" — байтовый диапазон 0..5// "hello" — byte range 0..5

Примеры из реального кодаReal-world examples

типичные сценарии использованияtypical usage scenarios
01

Обобщённая функция над любым непрерывным участком без копированияA generic function over any contiguous run, with no copying

fn sum(numbers: &[i32]) -> i32 вызывается одинаково с Vec, массивом или под-срезом — без дублирования кода и без единой лишней копии.fn sum(numbers: &[i32]) -> i32 is callable identically with a Vec, an array, or a sub-slice — with no code duplication and no copying whatsoever.

fn sum(numbers: &[i32]) -> i32 {
    numbers.iter().sum()
}

sum(&vec![1, 2, 3]);   // Vec// Vec
sum(&[1, 2, 3]);        // массив// array
sum(&vec![1, 2, 3, 4][1..3]);  // под-срез// sub-slice
02

&str принимает и String, и литералы, и подстроки одинаково&str accepts a String, a literal, and a substring identically

Функция, принимающая &str, работает и с String, и с &'static str-литералом, и со срезом-подстрокой без изменений. Гипотетическая функция, принимающая только &String, потребовала бы явного (ненужного) преобразования для литералов.A function accepting &str works unchanged on a String, a &'static str literal, and a substring slice. A hypothetical function accepting only &String would require an explicit (unnecessary) conversion for literals.

fn shout(s: &str) -> String { s.to_uppercase() }

shout("literal");                     // &'static str напрямую// &'static str directly
shout(&String::from("owned"));      // &String -> &str через deref coercion// &String -> &str via deref coercion
// fn shout(s: &String) потребовала бы &String::from("literal") для литерала// fn shout(s: &String) would require &String::from("literal") for a literal
03

Паника при срезе &str не по границе символаPanic when slicing &str at a non-char-boundary index

Срез &str индексируется байтами, но должен попадать на границу символа UTF-8 — иначе паника в рантайме, не ошибка компиляции. Для корректной работы с многобайтовыми символами используйте .chars() или .char_indices().A &str slice is byte-indexed but must land on a UTF-8 character boundary — otherwise it panics at runtime, not a compile error. Use .chars() or .char_indices() to work correctly with multi-byte characters.

let s = "héllo";         // 'é' занимает 2 байта в UTF-8// 'é' takes 2 bytes in UTF-8
// &s[0..2];  // ПАНИКА: byte index 2 is not a char boundary// &s[0..2];  // PANIC: byte index 2 is not a char boundary

let fixed: String = s.chars().take(2).collect();  // корректно для многобайтовых символов// correct for multi-byte characters
04

Срез как окно в чужой буфер без копированияA slice as a window into someone else's buffer, with no copy

Функция-парсер возвращает &[u8], ссылающийся на под-диапазон входного буфера, который остаётся у вызывающего, — без копирования данных. Время жизни возвращаемого среза привязано ко времени жизни входного буфера (см. lifetimes.html).A parser function returns a &[u8] referencing a sub-range of the input buffer that the caller still owns — with no data copied. The returned slice's lifetime is tied to the input buffer's (see lifetimes.html).

fn header<'a>(buffer: &'a [u8]) -> &'a [u8] {
    &buffer[0..4]  // окно в чужой буфер, без копирования// a window into the caller's buffer, no copy
}

ГраблиGotchas

Срез &str по не-границе символа паникует в рантайме, не на компиляцииSlicing &str at a non-char-boundary index panics at runtime, not compile time

Это НЕ отлавливается на этапе компиляции — реальный и регулярно встречающийся источник багов в коде, обрабатывающем не-ASCII текст (см. пример 3 выше).This is NOT caught at compile time — a real and recurring bug source in code that processes non-ASCII text (see example 3 above).

Срез — это заимствование, к нему применимы все правила borrow checker'аA slice is a borrow — all the usual borrow-checker rules apply

Нельзя одновременно держать &mut [T] и &[T] на перекрывающиеся диапазоны одних и тех же данных, даже если логически непересекающиеся под-диапазоны были бы безопасны по памяти. Это реальное ограничение, хотя стандартная библиотека предоставляет безопасный обходной путь — например, split_at_mut, который позволяет получить два независимых &mut-среза на непересекающиеся половины одного слайса.You can't hold a &mut [T] and a &[T] into overlapping ranges of the same underlying data simultaneously, even though logically non-overlapping sub-ranges would be memory-safe. This is a real limitation, though the standard library provides a safe escape hatch — e.g. split_at_mut, which lets you obtain two independent &mut slices into non-overlapping halves of one slice.

Длина среза не известна на этапе компиляции, в отличие от массива фиксированного размераA slice's length isn't known at compile time, unlike a fixed-size array

В отличие от [T; N], длина среза хранится в рантайме как поле толстого указателя — срезы годятся для данных рантайм-определённой длины, но не получают компилируемых проверок границ, которые даёт массив с const-generic размером (см. const-generics.html).Unlike [T; N], a slice's length is stored at runtime as a fat-pointer field — slices are suited to runtime-determined-length data but don't get the compile-time-checked bounds that a const-generic-sized array provides (see const-generics.html).