невладеющее представление непрерывного участка данныхa non-owning view over contiguous data
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.
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]
Slices решают задачу, которая до них требовала выбора между двумя плохими вариантами:Slices solve a problem that previously forced a choice between two bad options:
До 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.
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.
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.
Функция, принимающая &[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
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
Функция, принимающая &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
Срез &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
Функция-парсер возвращает &[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
}
Это НЕ отлавливается на этапе компиляции — реальный и регулярно встречающийся источник багов в коде, обрабатывающем не-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).
Нельзя одновременно держать &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.
В отличие от [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).