Rustのエラー E0382: borrow of moved value(ムーブ後の値の使用)
E0382 は、値を誰かに渡してしまった(ムーブした)あとで、もう一度その値を使おうとした、という意味のエラーです。Rust を始めて最初の1週間でいちばんよく出会うエラーで、ほとんどの場合は1行直すだけで解決します。このページでは、まず直し方を見せてから、何が起きているのかを説明します。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。
エラーメッセージ
Section titled “エラーメッセージ”error[E0382]: borrow of moved value: `name` --> src/main.rs:8:25 |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!("Goodbye, {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 valuehelp: consider cloning the value if the performance cost is acceptable |7 | greet(name.clone()); // ここで name は greet にムーブされる | ++++++++「borrow of moved value」は「ムーブされた値の借用」という意味です。note は「値を所有する必要がないなら、greet の引数を借用に変えてはどうか」と提案し、help は「値を clone してはどうか」と提案しています。
このエラーが出るのは、次のプログラムです。
fn greet(name: String) { println!("Hello, {name}!");}
fn main() { let name = String::from("Ferris"); greet(name); // ここで name は greet にムーブされる println!("Goodbye, {name}!"); // ❌ name はもうない}「use of moved value」(ムーブされた値の使用)という文言を見ることもあります。これも同じエラーです。あとの行が値を読むだけのとき(println! など)は「borrow」、もう一度ムーブしようとしたとき(let other = name; など)は「use」と表示されます。
エラーの意味
Section titled “エラーの意味”3つのラベルを上から順に読んでいきましょう。これだけで何が起きたのか全部わかります。
- 6行目:
nameはStringで、StringはCopyではありません。そのため、どこかに渡すと値がムーブされます。 - 7行目:
greet(name)で、文字列が関数の中にムーブされます。これで所有者はgreetになり、greetが終わると文字列は破棄されます。 - 8行目:
nameをもう一度読もうとしていますが、mainはもう持ち主ではありません。
家の鍵が1本しかないのに、それを友だちに渡してしまったようなものです。渡したあとは、自分が前に鍵を持っていたことを覚えていても、その鍵でドアを開けることはできません。
Rust がこうする理由
Section titled “Rust がこうする理由”Rust では、どの値にも所有者がちょうど1人います。所有者がいなくなると、値は片付けられます。こうして Rust は、ガベージコレクタなしでメモリを解放しています。このルールは所有権(ownership)のページで説明しています。
もし2つの変数が同じ String を所有していたら、最後に両方が同じメモリを解放しようとしてしまいます。これは「二重解放」(double free)と呼ばれる本物のバグで、C や C++ のプログラムがクラッシュする原因になります。Rust は、代入や関数呼び出しで値をムーブさせることで、所有者がいつも1人だけになるようにして、これを防いでいます。
i32、bool、char のような小さくて単純な型は Copy です。渡すと手軽にコピーされるので、これらの型で E0382 が出ることはありません。String や Vec、ほとんどの構造体のように、ヒープのメモリを持つ型は、コピーではなくムーブされます。
直し方1:& で借用する
Section titled “直し方1:& で借用する”関数が値を見るだけでいいときは、これを選びましょう。ほとんどの場合、これが正しい直し方です。引数を参照に変えて、&name を渡します。
fn greet(name: &str) { println!("Hello, {name}!");}
fn main() { let name = String::from("Ferris"); greet(&name); // 渡してしまわずに、貸すだけ println!("Goodbye, {name}!"); // ✅ まだ自分のもの}実行結果:
Hello, Ferris!Goodbye, Ferris!(エラーになる例と比べやすいように、ここでは表示する文字列を英語のままにしています。)
&String でも動きますが、文字列リテラルも受け取れるので、ふつうは &str を使います。詳しくは借用と文字列を見てください。関数の中で値を書き換える必要があるなら、代わりに &mut を使います。
直し方2:clone する
Section titled “直し方2:clone する”渡した先のコードが自分専用のコピーを本当に必要としていて(たとえば構造体や 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 を clone するのは安上がりで、まったく問題ありません。学んでいる最中ならなおさらです。気にするのは、大きなデータを何度も回るループの中で clone するときだけで十分です。とはいえ、借用(直し方1)で済むなら、そちらを選びましょう。
直し方3:小さな型には Copy を導出する
Section titled “直し方3:小さな型には Copy を導出する”座標や色、ID のように、単純な値だけでできた小さな構造体には、これを選びます。#[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:所有権を返してもらう
Section titled “直し方4:所有権を返してもらう”関数が値を受け取って書き換え、その結果がほしいときは、これを選びます。関数が値を返し、それをもう一度変数にしまいます。
fn add_signature(mut letter: String) -> String { letter.push_str("\n-- Ferris"); letter // 持ち主に返す}
fn main() { let letter = String::from("Rustaceanのみなさまへ"); let letter = add_signature(letter); // 返してもらう println!("{letter}");}実行結果:
Rustaceanのみなさまへ-- Ferrisこれは「ビルダー」スタイルの関数でよく使われます。その場でちょっと書き換えるだけなら、fn add_signature(letter: &mut String) のほうが読みやすいことも多いです。
よく出会う場面
Section titled “よく出会う場面”Vec を for ループで回すとき。 for task in tasks と書くと、ベクタ全体がループの中にムーブされるので、そのあと tasks は使えません。
for task in tasks { println!("実行中: {task}");}println!("{}件完了", tasks.len()); // ❌ `tasks` moved due to this implicit call to `.into_iter()`コメントのメッセージは「暗黙の .into_iter() 呼び出しによって tasks がムーブされた」という意味です。直し方:借用でループします(for task in &tasks)。詳しくはforとイテレータを見てください。
値を Vec や構造体に入れるとき。 names.push(name) も Order { customer } も、値を中にムーブします。そのあとで name や customer を使うとエラーになります。直し方:新しい置き場所から読み直す(names[0]、order.customer)、ほかの作業をムーブの前に済ませる、または .clone() したものを push します。
ループの中でムーブするとき。 log.push(message) がループの中にあると、1回目で message がムーブされ、2回目にはもう何も残っていません。コンパイラは value moved here, in previous iteration of loop(前回のループでここでムーブされた)と表示します。直し方:message.clone() を push するか、ループの中で値を作って、毎回新しい値が使えるようにします。
スレッドと move クロージャ。 thread::spawn(move || ...) は、キャプチャした変数をすべて新しいスレッドにムーブします。直し方:スレッドを作る前にデータを clone するか、Arc で共有します。詳しくは並行処理と共有状態を見てください。
関連するエラー
Section titled “関連するエラー”- E0502:値を借用したあと、その借用がまだ使われているうちに値を書き換えようとした。
- E0499:同じ値を同時に2回、可変で借用した。
- E0106:ライフタイムの指定がない。ムーブを避けようとして参照を返したときによく出ます。
- E0308:型の不一致。たとえば借用に切り替えたあとで、
&strが必要なところにStringを渡したとき。 - コンパイルエラーの一覧:エラー解説ページの全リストです。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. report が String のとき、save(report) を呼んだあとで report を表示しようとしました。なぜ E0382 になるのですか?
2. 関数は Vec<String> を読むだけです。ふつう、いちばんよい直し方はどれですか?
3. #[derive(Clone, Copy)] を付けられる構造体はどれですか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- E0382 — Rust error codes index(英語):このエラーの公式の説明です。ほかの例も載っています。
- 所有権とは? — The Rust Programming Language 日本語版:ムーブ、
Clone、Copyを順を追って説明している章です。 - 参照と借用 — The Rust Programming Language 日本語版:
&を使うと、値をムーブせずに使えるしくみです。 - 所有権とムーブ — Rust By Example 日本語版:ムーブと部分的なムーブを、実行できる短い例で紹介しています。
Copyトレイト — 標準ライブラリのドキュメント(英語):どの型がCopyなのか、CopyとCloneの違いがわかります。