Эта статья в блоге знакомит с понятиями типа String и заимствованного аналога &str, то есть срез строк, выделяя их различия. Далее приводится несколько внутренних деталей реализации.
String
String - это строка в кодировке UTF-8. Термин «владелец» означает, что переменная s типа String имеет право собственности на содержимое строки. Когда s выходит из области действия, строка владельца удаляется.
{ // Новая область
let s: String = String::from("My string"); // "My string" принадлежит s
...
} // Конец области, «s» и строка владельца «My string» удаляются
В стандартной библиотеке строка определяется следующим образом:
pub struct String {
vec: Vec<u8>,
}
Да, именно, строка - это просто вектор байтов (u8). Однако стандартная библиотечная реализация String гарантирует, что массив байтов, указанный vec, фактически содержит только действующие байты UTF-8.
&str
str - «строковый срез», последовательность байтов в памяти, которая, как известно, является допустимым текстом UTF-8. Это примитивный тип в Rust и большую часть времени используется в заимствованной форме &str.
Причина этого в том, что str не является размерным типом Sized, означает, что его размер не известен статически (во время компиляции), а компилятор Rust должен знать размер всех локальных переменных, чтобы гарантировать безопасность, предоставляемые языком Rust.
Фактически, если вы по пытаетесь создать собственную версию str:
fn main() {
let s: &str = "My string";
let owned_str: str = *s;
}
при построении предоставленного фрагмента кода (cargo build) компилятор возвращает следующую ошибку (протестирована на MacOS, Rust v1.70.0):
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> src/main.rs:3:9
|
3 | let owned_str = *s;
| ^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `str`
= note: all local variables must have a statically known size
= help: unsized locals are gated as an unstable feature
For more information about this error, try `rustc --explain E0277`.
То есть str не реализует признак Size и поэтому его размер не известен статически во время компиляции.
Заимствованная форма &str вместо этого является просто ссылкой, и ее размер всегда известен статически во время компиляции. Более точно &str является «толстым указателем», т.е. ссылкой на тип динамического размера (DST) (str), который содержит как указатель на данные, так и некоторую информацию, которая делает DST «полным» (т.е. длину str).
Это можно увидеть на простом примере. Рассмотрим следующий код:
fn foo(s: &str) {
println!("{}", s);
}
fn main() {
let s = String::from("My string");
let ss: &str = &s[0..5];
foo(&ss);
}
и скомпилировать его в режиме отладки (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>.
Это видно из следующего фрагмента кода, используемого для распечатки типа переменных:
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>())
}
fn main() {
let s: String = String::from("My string");
let s_ref = &s;
let s_str = s.as_str();
print_type_of(&s);
print_type_of(&s_ref);
print_type_of(&s_str);
}
соответствующий выход на консоль:
alloc::string::String
&alloc::string::String
&str
Обратите внимание на & перед второй строкой вывода.
Ссылка на строку (&String) отличается от ссылки &str. Однако &String часто может автоматически принудительно преобразовываться в &str.
Примеры
// Создание типа String из строкового литерала
let s = String::from("My string");
// Создать пустой тип String
let empty_s = String::new();
// Создать строку из вектора байтов
let hello_vec = vec![72, 101, 108, 108, 111];
let hello_string = String::from_utf8(hello_vec).unwrap();
// Создание строки, принадлежащей владельцу, из среза строки
let s: String = "My string".to_owned()
// Строковый литерал (str), действительный в течение всей программы
let my_string: &'static str = "My string";
// То же самое, что и выше, но короче
let my_string = "My string";
// При наличии строки создать срез строки, содержащий всю строку.
let s = String::from("My string");
let ss: &str = s.as_str();