Rust: с нуля до своего органайзера Урок 21 из 35
Изменяемое заимствование
Урок 21. Изменить строку через функцию и объяснить, когда другой доступ к ней снова разрешён.
Текст переведён с помощью ИИ.
Предварительно: уроки 19–20. Если непонятно, почему &title сохраняет владельца, вернитесь к уроку 20.
Образ и опорная карта
В библиотеке несколько человек могут одновременно читать один справочник. Если кто-то исправляет его страницу, на время исправления ему нужен отдельный доступ: другие читатели не должны видеть незавершённую правку. Изменяемое заимствование даёт временное право изменить значение без передачи владения. Образ ограничен: Rust проверяет использования ссылок в коде, а не выдаёт настоящие библиотечные пропуска.
Владелец mut → &mut → изменение через функцию → последнее использование ссылки → владелец снова работает. Закройте текст и восстановите эту цепочку. Уже знакомое let mut разрешает менять значение через его имя. Новое &mut title создаёт изменяемую ссылку, а &mut String в параметре описывает её тип. &mut пишется и при вызове, и в типе параметра; это не два изменения строки.
Первый пример
В проекте organizer сохраните прежний src/main.rs отдельно и замените его целиком. В папке с Cargo.toml выполните cargo run. Другие файлы и зависимости не меняются. Сначала предскажите строку результата; блок не содержит сообщений Cargo.
Метод push_str, знакомый по уроку 18, добавляет текст в конец String. Функция ниже получает временное право на это изменение; после её завершения владелец остаётся в main.
fn mark_done(title: &mut String) {
title.push_str(" — готово");
}
fn main() {
let mut title = String::from("Чтение");
mark_done(&mut title);
println!("Задача: {title}");
}
Задача: Чтение — готово
mark_done не владеет строкой и не возвращает её: ссылка разрешает изменить исходное значение. mut у владельца и &mut у ссылки решают разные задачи. Само наличие mut не делает каждую ссылку на строку изменяемой.
Одно право на изменение
Пока изменяемая ссылка ещё будет использоваться, нельзя одновременно держать ссылку для чтения на то же значение. Иначе читатель мог бы увидеть меняющееся содержимое. Следующий отдельный пример намеренно не компилируется; E0502 сообщает о несовместимых заимствованиях:
fn main() {
let mut title = String::from("План");
let reader = &title;
let writer = &mut title;
writer.push_str("!");
println!("{reader}");
}
Здесь reader нужен после создания writer, поэтому сроки двух доступов пересекаются. Исправление — закончить чтение до изменяемого заимствования. Не нужно слепо добавлять clone(): это сделало бы другую строку вместо изменения исходной.
fn main() {
let mut title = String::from("План");
let reader = &title;
println!("До: {reader}");
let writer = &mut title;
writer.push_str("!");
println!("После: {title}");
}
До: План
После: План!
Компилятор видит последнее использование reader перед созданием writer; имя может оставаться в том же блоке. Аналогично после последнего использования writer владелец снова доступен. Две одновременно используемые изменяемые ссылки на одно значение тоже запрещены. Это правило относится к пересекающимся использованиям, а не к запрету менять строку дважды за всю программу.
Проверка понимания
- Зачем нужны и
let mut title, и&mut title? - Почему второй пример с
readerне компилируется? - После какого действия можно снова читать
titleу владельца?
Проверьте: владелец разрешает изменение, ссылка передаёт это право временно; чтение reader пересекается с записью; после последнего использования изменяемой ссылки. Повторите карту без подсказки.
Задание
Обязательное. Напишите add_label(title: &mut String, label: &str), которая добавляет label к строке. Тип &str знаком по уроку 9: это доступ к тексту без владения; литерал "!" можно передать как &str. Создайте изменяемый String со словом План, вызовите функцию дважды с "!" и выведите План!!. Предскажите результат, затем запустите. Не используйте clone() и не возвращайте String из функции.
Граница. Поменяйте &mut String на &String в параметре и объясните отказ компилятора. Верните рабочую запись.
Подсказка
У владельца нужен let mut; в вызовах — add_label(&mut title, "!"). Внутри функции используйте title.push_str(label). Каждый вызов заканчивает своё заимствование до следующего.
Эталон после попытки
fn add_label(title: &mut String, label: &str) {
title.push_str(label);
}
fn main() {
let mut title = String::from("План");
add_label(&mut title, "!");
add_label(&mut title, "!");
println!("{title}");
}
План!!
Оба вызова меняют одну строку; её владелец остаётся в main. Официальный разбор изменяемых ссылок.
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.