Эта статья в блоге знакомит с понятиями типа `String` и заимствованного аналога `&str`, то есть срез строк, выделяя их различия. Далее приводится несколько внутренних деталей реализации.
String
`String` - это строка в кодировке UTF-8. Термин «владелец» означает, что переменная `s` типа `String` имеет право собственности на содержимое строки. Когда `s` выходит из области действия, строка владельца удаляется.
В стандартной библиотеке строка определяется следующим образом:
Да, именно, строка - это просто вектор байтов (`u8`). Однако стандартная библиотечная реализация `String` гарантирует, что массив байтов, указанный `vec`, фактически содержит только действующие байты UTF-8.
&str
`str` - «строковый срез», последовательность байтов в памяти, которая, как известно, является допустимым текстом UTF-8. Это примитивный тип в Rust и большую часть времени используется в заимствованной форме `&str`.
Причина этого в том, что `str` не является размерным типом `Sized`, означает, что его размер не известен статически (во время компиляции), а компилятор Rust должен знать размер всех локальных переменных, чтобы гарантировать безопасность, предоставляемые языком Rust.
Фактически, если вы по пытаетесь создать собственную версию `str`:
при построении предоставленного фрагмента кода (cargo build) компилятор возвращает следующую ошибку (протестирована на MacOS, Rust v1.70.0):
То есть `str` не реализует признак `Size` и поэтому его размер не известен статически во время компиляции.
Заимствованная форма `&str` вместо этого является просто ссылкой, и ее размер всегда известен статически во время компиляции. Более точно `&str` является «толстым указателем», т.е. ссылкой на тип динамического размера (DST) (`str`), который содержит как указатель на данные, так и некоторую информацию, которая делает DST «полным» (т.е. длину `str`).
Это можно увидеть на простом примере. Рассмотрим следующий код:
и скомпилировать его в режиме отладки (cargo build).
Теперь мы можем запустить получившийся исполняемый файл в отладчике и разместить точку останова непосредственно перед вызовом функции `foo` (на снимке экрана ниже показан отладчик Ghidra, работающий на ноутбуке Apple MacBook pro с процессором ARM).
Строковый срез (`&str`) `ss` создается командой по адресу `0x1000056cc (bl < >::index)` и помещается в стек (как и ожидалось, учитывая, что `ss` является локальной переменной). Как мы уже говорили выше, `ss` является жирным указателем и содержит как указатель на строковые данные, так и информацию о длине. Например, это можно наблюдать в консоли отладчика в левом нижнем углу изображения:
- Указатель стека, при считывании регистра `sp`, указывает на `0x000000016bddad60`;
- Информация о длине среза, распечатанная с помощью `x/1gx $sp+0x10`, равна `0x5`;
- Указатель на строковые данные, напечатанные с помощью `x/1gx $sp+0x8`, указывает на `0x00006000004ac010`;
- Мы можем распечатать значение, указанное этим адресом, в памяти `-s1 -fc -c 9 0x00006000004ac010` и убедиться, что этот адрес на самом деле является расположением ожидаемой строки.
&String
Обратите внимание, что если `s` является переменной типа `String`, `&s` является только ссылкой на `String` (т. е. `&String`). Концептуально это то же самое, что происходит, когда у вас есть переменная `v` типа `Vec<T>` и вы берете ссылку `&v`. В итоге появляется ссылка на `Vec<T>`.
Это видно из следующего фрагмента кода, используемого для распечатки типа переменных:
соответствующий выход на консоль:
Обратите внимание на `&` перед второй строкой вывода.
Ссылка на строку (`&String`) отличается от ссылки `&str`. Однако `&String` часто может автоматически принудительно преобразовываться в `&str`.
Примеры
Ссылки
Статья на rusty-code.ru