Rust: с нуля до своего органайзера Урок 19 из 20
Владение и перемещение
Урок 19. Объяснить, почему переданная строка перестаёт быть доступна под старым именем.
Текст переведён с помощью ИИ.
Предварительно: уроки 1–18. Повторите область видимости и память, если непонятно, когда имя перестаёт быть доступным.
Правило и образ
У значения String есть владелец: место в программе, отвечающее за управляемый ресурс строки. Когда такое значение передают новому имени, владение обычно перемещается. Представьте единственный талон на хранение вещи: после передачи талона прежний держатель больше не распоряжается ею. Граница образа: Rust не отправляет байты через комнату и не гарантирует физического перемещения адреса; правило касается того, какое имя разрешено использовать.
Опорная карта: создать → владеть → передать → прежнее имя недоступно → новый владелец освобождает ресурс. Расскажите эту цепочку до запуска примера.
Увидьте перемещение
В проекте organizer из урока 6 сохраните старый src/main.rs, замените его целиком и выполните cargo run в папке с Cargo.toml. Блок результата — вывод программы, без сообщений Cargo.
fn print_title(title: String) {
println!("Задача: {title}");
}
fn main() {
let title = String::from("Чтение");
print_title(title);
}
Задача: Чтение
Параметр title: String получает владение строкой при вызове. Внутри функции его можно использовать. После вызова старое имя в main уже не даёт доступ к строке. По завершении функции строковый ресурс будет освобождён автоматически. Попытка использовать старое имя даёт E0382:
fn print_title(title: String) {
println!("{title}");
}
fn main() {
let title = String::from("Чтение");
print_title(title);
println!("{title}");
}
Компилятор не сообщает, что строка «пустая»; он запрещает использовать имя после перемещения. Так исключается обращение к ресурсу, который уже мог быть освобождён.
Почему числа ведут себя иначе
fn print_minutes(minutes: u32) {
println!("В функции: {minutes}");
}
fn main() {
let minutes = 15;
print_minutes(minutes);
println!("Снаружи: {minutes}");
}
В функции: 15
Снаружи: 15
Тип u32 реализует трейт Copy. Трейт описывает свойство или поведение типа; свой трейт напишем в уроке 46. Copy разрешает неявно создавать независимую копию значения при передаче. Старое имя остаётся доступным. Массивы из Copy-элементов тоже могут быть Copy; нельзя выводить из этого, что любая коллекция или строка копируется.
Если действительно нужны два независимых владельца строки, можно явно вызвать clone(). Это создаёт копию строковых данных и может потребовать выделения памяти:
fn main() {
let first = String::from("Чтение");
let second = first.clone();
println!("Первая: {first}");
println!("Вторая: {second}");
}
Первая: Чтение
Вторая: Чтение
Метод clone() здесь нужен ради двух владельцев, а не для исправления любой ошибки компилятора. Если функция только читает строку, в следующем уроке мы передадим ей ссылку без копирования.
Проверка по памяти
Предскажите, какое имя допустимо после let b = a;, если a имеет тип String; затем ответьте для u32. Ответ: для String доступно b, но не a; для u32 доступны оба имени с независимыми числовыми значениями. Проверьте рассуждение маленьким опытом.
Задание
Перед заданием ещё одна форма уже знакомого mut: в объявлении fn decorate(mut title: String) он относится к имени параметра внутри функции. Функция получит владение значением и сможет добавить к нему текст. Это не делает исходное имя в main снова доступным после передачи.
Обязательное. Напишите decorate(mut title: String) -> String, которая добавляет к строке ! через push_str и возвращает её. В main передайте String::from("План") в функцию, сохраните возвращённое значение и выведите План!. Объясните вслух: где был владелец до вызова, где во время вызова, где после возврата. Избегайте clone(): здесь достаточно передачи туда и обратно.
Подсказка
В параметре укажите mut title: String: здесь уже знакомое mut из урока 9 разрешает изменять именно имя параметра внутри функции. Последним выражением функции напишите title без ;. Старое имя после вызова не используйте; результат присвойте новому.
Эталон после попытки
fn decorate(mut title: String) -> String {
title.push_str("!");
title
}
fn main() {
let original = String::from("План");
let decorated = decorate(original);
println!("{decorated}");
}
План!
Здесь владение проходит от original к параметру, затем к decorated. Официальный разбор владения.
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.