Rust: с нуля до своего органайзера Урок 18 из 20
Значения и память
Урок 18. Различить значение, его владельца и место хранения данных до изучения перемещения.
Текст переведён с помощью ИИ.
Предварительно: уроки 1–17. Если область видимости неясна, повторите урок 9; если функция и вызов — урок 14.
Почему вопрос возник сейчас
С массивами чисел мы передавали значения в функции без заметных последствий. С растущим текстом потребуется понимать, кто отвечает за память. Память — место, где программа хранит данные во время работы. Буфер — выделенная область для данных, например байтов текста; его размер может меняться. Переменная — имя, через которое код обращается к значению; она не обязана быть отдельной «коробкой» с полной копией данных. Реальное размещение зависит от компилятора и обстоятельств, поэтому схема ниже объясняет правила языка, а не точные адреса каждого байта.
Опорная карта: имя → значение → при необходимости буфер данных → владелец → конец области видимости. Закройте карту и восстановите её по памяти. Внешний буфер не означает, что каждое значение всегда лежит в двух местах.
Сравните фиксированное и растущее
Сначала разберём новые записи. String — тип владеющего строковыми данными значения, которое может расти. String::from("План") создаёт его из строкового литерала; двойное двоеточие :: выбирает связанную с типом функцию, а не соединяет строки. title.push_str(" на день") добавляет текст в конец. Точка вызывает метод, как len() в уроке 15. let mut нужен, поскольку мы меняем title: сам тип String не делает каждую переменную изменяемой. Разные строковые типы подробно сравним в уроке 22.
В проекте organizer из урока 6 сохраните прежний src/main.rs, замените его целиком следующим кодом и выполните cargo run в папке с Cargo.toml. В примере нет дополнительных зависимостей. Блок результата показывает только вывод программы.
fn main() {
let fixed: [u32; 2] = [10, 15];
let mut title = String::from("План");
title.push_str(" на день");
println!("{} и {} минут", fixed[0], fixed[1]);
println!("{title}");
}
10 и 15 минут
План на день
[u32; 2] имеет известный размер и содержит два числа прямо в значении массива. String содержит служебные сведения о тексте и владеет буфером байтов, размер которого может меняться. Когда буферу требуется больше места, он может быть выделен заново. Не запоминайте конкретные адреса: они не часть обещания программы.
Стек, куча и срок
Стек обычно хранит данные вызовов функций с известным размером; куча позволяет выделять область нужного размера во время работы. В нашей упрощённой схеме локальные данные фиксированного размера находятся в области вызова, а байты растущей строки — в выделенном буфере. Компилятор вправе оптимизировать фактическое размещение, пока видимое поведение программы сохраняется. Ни стек, ни куча не определяют сами по себе, можно ли использовать имя после передачи значения: это решают правила владения.
Область видимости имени заканчивается на соответствующей закрывающей фигурной скобке. Владелец ресурса отвечает за его освобождение, когда значение больше не нужно. В Rust обычное освобождение управляемых значений происходит автоматически; строковый буфер вручную освобождать не нужно. У чисел и массивов чисел такого выделенного строкового буфера нет.
fn main() {
let outer = 10;
{
let inner = String::from("заметка");
println!("{inner}: {outer}");
}
println!("Внешнее значение: {outer}");
}
заметка: 10
Внешнее значение: 10
После внутренней } имя inner недоступно. Внешнее имя outer доступно дальше. Если попытаться напечатать inner после скобки, компилятор выдаст E0425:
fn main() {
{
let inner = String::from("заметка");
println!("{inner}");
}
println!("{inner}");
}
Проверьте понимание
Предскажите: растёт ли длина массива после push_str? Можно ли использовать outer за внутренней скобкой? Почему рисунок со стеком и кучей не стоит принимать за точную карту адресов? Ответы: массив не меняется; да; размещение может зависеть от оптимизации, а рисунок показывает модель работы с данными.
Задание
Обязательное. Создайте String со словом Задача, добавьте : чтение через push_str; во внутреннем блоке объявите массив [5, 10] и выведите его сумму. После блока выведите строку. Сначала нарисуйте границы видимости имён. Ожидаемый вывод: Минуты: 15, затем Задача: чтение. Проверьте cargo run, затем объясните, почему имя массива после внутреннего блока недоступно.
Подсказка
Строку объявите до внутреннего блока как let mut title = String::from(...). Массив объявите внутри блока. Строка переживёт этот блок, поскольку её имя находится во внешней области видимости.
Эталон после попытки
fn main() {
let mut title = String::from("Задача");
title.push_str(": чтение");
{
let minutes = [5, 10];
println!("Минуты: {}", minutes[0] + minutes[1]);
}
println!("{title}");
}
Минуты: 15
Задача: чтение
Следующий урок объяснит, что происходит при передаче самого значения String другому имени. Справка Rust о владении.
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.