Ошибка Rust E0382: borrow of moved value (использование перемещённого значения)
E0382 значит, что ты отдал значение (ты его переместил, по-английски moved), а потом попытался использовать его снова. Это самая частая ошибка первой недели с Rust, и почти всегда она исправляется одной строкой. Сначала на этой странице показано исправление, а потом объяснение, что происходит. Другие ошибки из этой серии собраны на странице Ошибки компилятора Rust.
Сообщение об ошибке
Заголовок раздела «Сообщение об ошибке»error[E0382]: borrow of moved value: `name` --> src/main.rs:8:22 |6 | let name = String::from("Ferris"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait7 | greet(name); // здесь name перемещается в greet | ---- value moved here8 | println!("Пока, {name}!"); // ❌ name больше нет | ^^^^ value borrowed here after move |note: consider changing this parameter type in function `greet` to borrow instead if owning the value isn't necessary --> src/main.rs:1:16 |1 | fn greet(name: String) { | ----- ^^^^^^ this parameter takes ownership of the value | | | in this functionhelp: consider cloning the value if the performance cost is acceptable |7 | greet(name.clone()); // здесь name перемещается в greet | ++++++++«Borrow of moved value» значит «заимствование перемещённого значения». Заметка note предлагает, чтобы greet только заимствовала значение, а не забирала его во владение. Подсказка help предлагает клонировать значение, если цена по производительности приемлема.
Вот программа, которая выдаёт эту ошибку:
fn greet(name: String) { println!("Привет, {name}!");}
fn main() { let name = String::from("Ferris"); greet(name); // здесь name перемещается в greet println!("Пока, {name}!"); // ❌ name больше нет}Иногда встречается и формулировка «use of moved value» («использование перемещённого значения»). Это та же самая ошибка. Rust пишет «borrow», когда следующая строка только читает значение (как println!), и «use», когда она снова пытается его переместить (как let other = name;).
Что это значит
Заголовок раздела «Что это значит»Прочитай три пометки сверху вниз. Они рассказывают всю историю:
- Строка 6:
name— этоString, аStringне реализуетCopy. Поэтому, если передать её куда-то, она перемещается. - Строка 7:
greet(name)перемещает строку в функцию. Теперь ей владеетgreet, и строка удаляется, когдаgreetзаканчивается. - Строка 8: ты пытаешься снова прочитать
name, ноmainею больше не владеет.
Представь, что ты отдал другу единственный ключ от дома. Отдав его, ты уже не откроешь им дверь, хотя прекрасно помнишь, что он у тебя был.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»У каждого значения в Rust ровно один владелец. Когда владелец исчезает, значение убирается. Так Rust освобождает память без сборщика мусора. Правила объясняются в уроке Владение.
Если бы одной и той же String владели две переменные, обе в конце попытались бы освободить одну и ту же память. Это «двойное освобождение» (double free) — настоящий баг, из-за которого падают программы на C и C++. Rust его предотвращает: присваивание и вызов функции перемещают значение, так что владелец всегда один.
Маленькие простые типы вроде i32, bool и char реализуют Copy: при передаче создаётся дешёвая копия, и E0382 для них не бывает. Типы, которые владеют памятью в куче, — String, Vec и большинство структур — вместо этого перемещаются.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: заимствуй через &
Заголовок раздела «Исправление 1: заимствуй через &»Выбирай этот вариант, когда функции нужно только посмотреть на значение. Чаще всего правильное исправление именно это. Сделай параметр ссылкой и передавай &name:
fn greet(name: &str) { println!("Привет, {name}!");}
fn main() { let name = String::from("Ferris"); greet(&name); // одалживаем, а не отдаём println!("Пока, {name}!"); // ✅ всё ещё наше}Вывод:
Привет, Ferris!Пока, Ferris!&String тоже сработает, но обычно выбирают &str, потому что такой параметр принимает и строковые литералы. См. Заимствование и Строки. Если функции нужно изменить значение, используй &mut.
Исправление 2: клонируй
Заголовок раздела «Исправление 2: клонируй»Выбирай этот вариант, когда другому коду действительно нужна своя копия — например, чтобы сохранить её в структуре или в Vec, — а оригинал тебе тоже ещё нужен. .clone() создаёт полную независимую копию:
struct Player { name: String, score: u32,}
fn main() { let name = String::from("Ferris"); let player = Player { name: name.clone(), score: 0 }; println!("{} начинает с {} очками", player.name, player.score); println!("Исходное имя: {name}");}Вывод:
Ferris начинает с 0 очкамиИсходное имя: FerrisБудем честны: клонировать короткую строку или маленький Vec дёшево, и это совершенно нормально, особенно пока ты учишься. Беспокоиться стоит, только если ты клонируешь большие данные внутри часто выполняемого цикла. Если подходит заимствование (исправление 1), выбирай его.
Исправление 3: выведи Copy для маленьких типов
Заголовок раздела «Исправление 3: выведи Copy для маленьких типов»Выбирай этот вариант для маленьких структур, состоящих только из простых значений: координат, цветов, идентификаторов. Добавь #[derive(Clone, Copy)], и структура будет копироваться, а не перемещаться:
#[derive(Debug, Clone, Copy)]struct Point { x: i32, y: i32,}
fn show(p: Point) { println!("({}, {})", p.x, p.y);}
fn main() { let start = Point { x: 0, y: 3 }; show(start); // копирует start show(start); // ✅ снова работает println!("{:?}", start);}Вывод:
(0, 3)(0, 3)Point { x: 0, y: 3 }Copy работает, только если каждое поле тоже Copy. Структура с полем String или Vec не может быть Copy — для неё используй Clone и вызывай .clone(). Подробнее — в уроке Популярные трейты.
Исправление 4: верни владение обратно
Заголовок раздела «Исправление 4: верни владение обратно»Выбирай этот вариант, когда функция принимает значение, меняет его, и тебе нужен результат. Функция возвращает значение, а ты снова его сохраняешь:
fn add_signature(mut letter: String) -> String { letter.push_str("\n-- Ferris"); letter // отдаём обратно}
fn main() { let letter = String::from("Дорогой друг,"); let letter = add_signature(letter); // забираем обратно println!("{letter}");}Вывод:
Дорогой друг,-- FerrisЭто хорошо подходит для функций в стиле «строителя» (builder). Для простого изменения на месте часто проще читать fn add_signature(letter: &mut String).
Где это обычно встречается
Заголовок раздела «Где это обычно встречается»Цикл for по Vec. for task in tasks перемещает весь вектор в цикл, поэтому после него tasks использовать нельзя:
for task in tasks { println!("Делаю: {task}");}println!("Готово задач: {}", tasks.len()); // ❌ `tasks` moved due to this implicit call to `.into_iter()`Комментарий повторяет пояснение компилятора: tasks перемещён из-за неявного вызова .into_iter(). Исправление: перебирай заимствование, for task in &tasks. См. Цикл for и Итераторы.
Значение кладут в Vec или в структуру. И names.push(name), и Order { customer } перемещают значение внутрь. Использовать после этого name или customer не получится. Исправление: читай значение из его нового дома (names[0], order.customer), сделай остальную работу до перемещения или клади .clone().
Перемещение внутри цикла. Если log.push(message) стоит внутри цикла, первый проход перемещает message, и второму уже ничего не остаётся. Компилятор пишет value moved here, in previous iteration of loop («значение перемещено здесь, на предыдущей итерации цикла»). Исправление: клади message.clone() или создавай значение внутри цикла, чтобы на каждом проходе было новое.
Потоки и замыкания move. thread::spawn(move || ...) перемещает каждую захваченную переменную в новый поток. Исправление: клонируй данные до запуска потока или раздели их через Arc. См. Многопоточность и Общее состояние.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0502: ты заимствовал значение, а потом попытался его изменить, пока заимствование ещё используется.
- E0499: два изменяемых заимствования одного значения одновременно.
- E0106: не указано время жизни; часто встречается, когда возвращаешь ссылку, чтобы избежать перемещения.
- E0308: несовпадение типов, например
Stringтам, где после перехода на заимствование ожидается&str. - Все ошибки компилятора: полный список страниц об ошибках.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Ты вызываешь save(report), где report — это String, а потом выводишь report. Почему появляется E0382?
2. Функции нужно только читать Vec<String>. Какое исправление обычно лучше всего?
3. Какая структура может использовать #[derive(Clone, Copy)]?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0382 — Rust error codes index (на английском): официальное объяснение этой ошибки с дополнительными примерами.
- Что такое владение? — книга «Язык программирования Rust» (перевод на русский): глава, которая шаг за шагом объясняет перемещение,
CloneиCopy. - Ссылки и заимствование — книга «Язык программирования Rust» (перевод на русский): как
&позволяет пользоваться значением, не перемещая его. - Владение и перемещение — Rust на примерах (перевод на русский): короткие примеры перемещения и частичного перемещения, которые можно запустить.
- Трейт
Copy— документация стандартной библиотеки (на английском): какие типы реализуютCopyи чемCopyотличается отClone.