Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Строки

Note

Если эта глава кажется вам трудной при первом прочтении, не пытайтесь понять её полностью. Вернитесь к ней после прочтения глав про Владение и Структуры.

Строки в Rust хранятся в виде буферов с символами в UTF-8 кодировке. При этом в Rust есть два основных типа для строк, которые отличаются тем, как они взаимодействуют с этим буфером: &str и String.

&str (строковый слайс)

Если мы напишем в коде программы строковый литерал (строку в двойных кавычках), то эта строка будет иметь тип &str.

#![allow(unused)]
fn main() {
let s: &str = "some text";
}

По сути &str — это слайс, ссылающийся на буфер с последовательностью символов в кодировке UTF-8.
В каком-то смысле такие строки похожи на const char* строки в языке C, с той разницей, что в отличие от C, &str, будучи слайсом, хранит не только начальный адрес буфера в памяти, но и его длину.

Когда компилятор находит в коде строковый литерал, он, как правило, помещает эту строку в сегмент данных (или в сегмент кода, в зависимости от целевой платформы), и там эта строка “живёт” от самого начала программы и до её конца.

Тип &str не занимается управлением памяти, в которой находится строка, он просто ссылается на данные в памяти. При этом эта память может принадлежать как сегменту данных, так и куче (и являться собственностью объекта String) или даже располагаться на стеке.

String

Если &str — это слайс, который ссылается на буфер со строкой, но никак не управляет этим буфером, то String, наоборот, является собственником буфера, в котором содержится строка.

Технически String является обёрткой над вектором Vec<u8>, который хранит последовательность символов в кодировке UTF-8. Поэтому String всегда единолично владеет буфером со своей строкой, и этот буфер всегда располагается в куче.

При этом для любого String всегда можно создать слайс &str, который будет ссылаться на строковый буфер, находящийся во владении String.

Есть 3 способа создать переменную типа String:

  • При помощи конструктора String::from
  • При помощи String::new создать пустую строку и далее наполнить её отдельно
  • Из &str при помощи метода to_string()

Конструктор String::from

Наиболее понятный способ создания объекта String — функция-конструктор (о них мы поговорим в главе Структуры) String::from(&str), которая в качестве аргумента принимает слайс &str. Эта функция:

  1. создаёт объект String, который как мы уже сказали, — просто обёртка над Vec<u8> (вектором хранящим буфер с элементами типа u8)
  2. далее из аргумента &str копирует строку в свежесозданный вектор
  3. и после возвращает готовый, инициализированный объект String
fn main() {
    // слайс на статическую строку, находящуюся в сегменте даных
    let slice: &str = "text";

    // Создаст в куче буфер и скопирует в него "text".
    // На стеке будет тройка значений, как у Vec:
    // адрес буфера, общий размер буфера и количество заполненных строкой байт
    let s = String::from(slice);
}

Note

Разумеется, гораздо короче и проще написать просто String::from("text"), что в большинстве случаев и делают. В примере выше мы создали отдельную переменную slice исключительно для наглядности.

Конструктор String::new

Функция-конструктор String::new просто создаёт новый объект String с пустым строковым буфером. Это может быть нужно, например, для того, чтобы передать объект String в функцию, которая заполнит его текстом.

Например, функция, которая читает строку с консоли, в качестве параметра принимает мутабельную ссылку на объект String, в который будет записан текст, считанный с консоли.

fn main() {
    println!("Please enter some text and hit Enter button");

    let mut buf = String::new(); // создаём пустую строку
    std::io::stdin().read_line(&mut buf); // считываем текст с консоли в buf

    println!("You have entered: {buf}");
}

Также в пустую строку (да и не только в пустую) можно добавлять символы при помощи метода push(char) или сразу строковые слайсы при помощи метода push_str(&str).

fn main() {
    let mut s = String::new();
    s.push('H');
    s.push('e');
    s.push('l');
    s.push('l');
    s.push('o');
    s.push_str(" world!");

    println!("{s}"); // Hello world!
}

Метод to_string()

Последний способ создания объекта String — вызов метода to_string() на объекте слайса &str. По сути, этот метод делает абсолютно то же самое, что и String::from(&str), только с другим синтаксисом.

fn main() {
    let s: String = "text".to_string();
}

&str и String в памяти

Чтобы подытожить, как &str и String располагаются в памяти, давайте рассмотрим следующий пример, в котором мы:

  1. Создаём слайс &str из строкового литерала
  2. Далее создаём String из этого слайса
  3. Создаём слайс, ссылающийся на строковый буфер объекта String
fn main() {
  // Компилятор увидит константный строковый литерал и поместит
  // такую строку в сегмент статических данных.
  let a_slice_1: &str = "text";

  // Создаём String из символов, на которые указывает слайс.
  // Это приведёт к созданию копии символов строки в куче.
  let a_string: String = String::from(a_slice_1);

  // Создаём второй слайс, который указывает на буфер символов в хипе,
  // принадлежащей String-строке.
  let a_slice_2: &str = a_string.as_str();
}

Примерно так эти строки будут располагаться в памяти:

Макрос format!

Мы уже знакомы с макросом println!, который используется для вывода на консоль. Этот макрос в качестве аргумента принимает форматирующую строку, в которую при помощи {} можно “встраивать” значения.

Стандартная библиотека предлагает еще один макрос — format!. Он принимает на вход такую же форматирующую строку, как и println!, только, в отличие от последнего, он не печатает текст на консоль, а возвращает его в виде объекта String.

fn main() {
    let s: String = format!("{} in the power of the 2 is {}", 3, 9);
    println!("{s}"); // 3 in the power of the 2 is 9
}