Перейти к содержимому

Ошибка 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` trait
7 | greet(name); // здесь name перемещается в greet
| ---- value moved here
8 | 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 function
help: 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;).

Прочитай три пометки сверху вниз. Они рассказывают всю историю:

  1. Строка 6: name — это String, а String не реализует Copy. Поэтому, если передать её куда-то, она перемещается.
  2. Строка 7: greet(name) перемещает строку в функцию. Теперь ей владеет greet, и строка удаляется, когда greet заканчивается.
  3. Строка 8: ты пытаешься снова прочитать name, но main ею больше не владеет.

Представь, что ты отдал другу единственный ключ от дома. Отдав его, ты уже не откроешь им дверь, хотя прекрасно помнишь, что он у тебя был.

У каждого значения в Rust ровно один владелец. Когда владелец исчезает, значение убирается. Так Rust освобождает память без сборщика мусора. Правила объясняются в уроке Владение.

Если бы одной и той же String владели две переменные, обе в конце попытались бы освободить одну и ту же память. Это «двойное освобождение» (double free) — настоящий баг, из-за которого падают программы на C и C++. Rust его предотвращает: присваивание и вызов функции перемещают значение, так что владелец всегда один.

Маленькие простые типы вроде i32, bool и char реализуют Copy: при передаче создаётся дешёвая копия, и E0382 для них не бывает. Типы, которые владеют памятью в куче, — String, Vec и большинство структур — вместо этого перемещаются.

Выбирай этот вариант, когда функции нужно только посмотреть на значение. Чаще всего правильное исправление именно это. Сделай параметр ссылкой и передавай &name:

fn greet(name: &str) {
println!("Привет, {name}!");
}
fn main() {
let name = String::from("Ferris");
greet(&name); // одалживаем, а не отдаём
println!("Пока, {name}!"); // ✅ всё ещё наше
}

Вывод:

Привет, Ferris!
Пока, Ferris!
▶ Попробуй сам

&String тоже сработает, но обычно выбирают &str, потому что такой параметр принимает и строковые литералы. См. Заимствование и Строки. Если функции нужно изменить значение, используй &mut.

Выбирай этот вариант, когда другому коду действительно нужна своя копия — например, чтобы сохранить её в структуре или в 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(). Подробнее — в уроке Популярные трейты.

Выбирай этот вариант, когда функция принимает значение, меняет его, и тебе нужен результат. Функция возвращает значение, а ты снова его сохраняешь:

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