В Rust срез представляет собой непрерывный блок однородно типизированных данных различной длины.
Что это значит?
[u8]
- это срез. В памяти это блок u8
с. Сам срез является данными. Однако часто люди называют &[u8]
срезом. &[u8]
- это указатель на этого блока данных. Этот указатель содержит две вещи: указатель на сами данные и длину данных. Поскольку он содержит две вещи, он называется жирным указателем. &u8
также является ссылкой (также может рассматриваться как указатель в данном случае *), но мы уже знаем, что на что бы он ни указывал, будет один u8
. Следовательно, это тонкий указатель, поскольку он имеет только один элемент.
Вам гарантировано, что все данные в [u8]
имеют тип u8
.
Поскольку ваш [u8]
только что определен как непрерывный блок памяти типа u8
, не существует определения времени компиляции относительно его размера. Следовательно, нам нужно , чтобы сохранить его длину в указателе на него. Мы также не можем поместить его в стек (это означает: у нас не может быть локальной переменной, которая является просто [u8]
**).
Расширение:
- A
[T]
- это срез T
с. Для любого данного T
, пока T
сам по себе является типом с размером ***, мы можем представить тип [T]
. - A
str
- это фрагмент строки. Он гарантированно будет действительным текстом UTF-8, и это все, что отделяет его от [u8]
. Rust мог сбросить действительную гарантию UTF-8 и просто определить все остальное в str
как часть [u8]
.
Ну, поскольку вы не можете владеть срезом локально ****, вам может быть интересно, как мы создаем срезы.
Ответ таков: мы помещаем данные во что-то уже известного размера, а , а затем заимствуем фрагменты из этого.
Возьмем, к примеру:
let my_array: [u32; 3] = [1, 2, 3];
Мы можем нарезать my_array
на [u32]
примерно так:
let my_slice: [u32] = my_array[..];
Но так как мы не можем владеть локальная переменная, размер которой еще не известен, мы должны поставить ее под ссылкой:
let my_slice: &[u32] = &my_array[..];
Суть среза в том, что это очень гибкий (за исключением времени жизни) метод работы с непрерывными блоками данные, независимо от того, откуда данные поступают. Я мог бы так же легко сделать my_array
a Vec<u8>
, который выделен в куче, и он все равно работал бы.
В чем разница между & String и & str?
&String
является ссылкой на весь строковый объект. Строковый объект в Rust по сути является Vec<u8>
. Vec
содержит указатель на данные, которые он «содержит», поэтому ваш &String
может рассматриваться как &&str
. И именно поэтому мы можем сделать одно из следующих действий:
let my_string: String = "Abc".to_string();
let my_str: &str = &my_string[..]; // As explained previously
// OR
let my_str: &str = &*my_string;
Объяснение этого приводит меня к вашему последнему вопросу:
Что такое черта разыменования?
Черта Deref
- это черта, которая описывает оператор разыменования (*
). Как вы видели выше, я смог сделать *my_string
. Это потому, что String
реализует Deref
, что позволяет разыменовывать String
. Точно так же я могу разыменовать Vec<T>
в [T]
.
Обратите внимание, что черта Deref
используется в большем количестве мест, чем просто *
:
let my_string: String = "Abc".to_string();
let my_str: &str = &my_string;
Если я пытаюсь присвоить значение типа &T
в место типа &U
, тогда Rust будет пытаться разыменовать мой T
столько раз, сколько потребуется, чтобы получить U
, сохраняя при этом хотя бы одну ссылку. Точно так же, если бы у меня был &&&&....&&&&T
, и я попытался присвоить его &&&&....&&&&U
, он все равно работал бы.
Этот называется принудительным приведением в действие: автоматическое включение &T
в &U
, где некоторое количество *T
приведет к U
.
- *: необработанные указатели
*const T
и *mut T
имеют тот же размер, что и ссылки, но компилятор считает их непрозрачными. Компилятор не дает никаких гарантий относительно того, что находится за необработанным указателем, или что они даже правильно выровнены. Следовательно, они небезопасны для разыменования. Но поскольку черта Deref
определяет метод deref
, который является безопасным, разыменование необработанного указателя является специальным и также не будет выполняться автоматически. - **: сюда входят и другие типы динамического размера, например в качестве признаков и
extern type
s. Это также включает в себя struct
s, которые содержат динамически изменяемый тип в качестве своего последнего члена, хотя их очень трудно правильно построить, но в будущем это станет легче с чертой CoerceUnsized
. Можно сделать все это недействительным (за исключением extern type
s) с использованием ночной функции unsized_locals
, которая позволяет некоторое использование локально динамически изменяемых размеров. - ***: все типы с типом размера чей размер известен во время компиляции. Вы можете идентифицировать их в общем; учитывая тип
T
, размер T
известен во время компиляции, если T: Sized
. Если T: ?Sized
, то его размер может быть неизвестен во время компиляции (T: ?Sized
является наиболее гибким требованием для вызывающих, так как он принимает что угодно ). Поскольку для среза требуется, чтобы данные внутри были смежными и однородными по размеру и типу, типы динамического размера (или !Sized
) невозможно содержать внутри среза, массива или Vec<T>
и поддерживать O(1)
индексация. Хотя Rust, вероятно, мог бы написать специальный код для индексации в группу динамически изменяемых типов, в настоящее время это не так. - ****: На самом деле вы можете иметь срез, он просто должен находиться под указателем, которому он принадлежит. Это может быть, например,
Box<[T]>
или Rc<[T]>
. Они освободят срез самостоятельно (A Box
при отбрасывании и Rc
, когда отбрасываются все сильные и слабые ссылки Rc
(Деструктор значения вызывается, когда отбрасываются все сильные ссылки, но память не освобождается, пока не исчезнут все слабые ссылки.)).