Shanraq.org Shanraq.org
Владение и перемещение
IT

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)

Пока нет комментариев. Будьте первым.