コンテンツにスキップ

Rustのエラー E0382: borrow of moved value(ムーブ後の値の使用)

E0382 は、値を誰かに渡してしまった(ムーブした)あとで、もう一度その値を使おうとした、という意味のエラーです。Rust を始めて最初の1週間でいちばんよく出会うエラーで、ほとんどの場合は1行直すだけで解決します。このページでは、まず直し方を見せてから、何が起きているのかを説明します。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

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` trait
7 | greet(name); // ここで name は greet にムーブされる
| ---- value moved here
8 | 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 value
help: 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」と表示されます。

3つのラベルを上から順に読んでいきましょう。これだけで何が起きたのか全部わかります。

  1. 6行目: name は String で、String は Copy ではありません。そのため、どこかに渡すと値がムーブされます。
  2. 7行目: greet(name) で、文字列が関数の中にムーブされます。これで所有者は greet になり、greet が終わると文字列は破棄されます。
  3. 8行目: name をもう一度読もうとしていますが、main はもう持ち主ではありません。

家の鍵が1本しかないのに、それを友だちに渡してしまったようなものです。渡したあとは、自分が前に鍵を持っていたことを覚えていても、その鍵でドアを開けることはできません。

Rust では、どの値にも所有者がちょうど1人います。所有者がいなくなると、値は片付けられます。こうして Rust は、ガベージコレクタなしでメモリを解放しています。このルールは所有権(ownership)のページで説明しています。

もし2つの変数が同じ String を所有していたら、最後に両方が同じメモリを解放しようとしてしまいます。これは「二重解放」(double free)と呼ばれる本物のバグで、C や C++ のプログラムがクラッシュする原因になります。Rust は、代入や関数呼び出しで値をムーブさせることで、所有者がいつも1人だけになるようにして、これを防いでいます。

i32、bool、char のような小さくて単純な型は Copy です。渡すと手軽にコピーされるので、これらの型で E0382 が出ることはありません。String や Vec、ほとんどの構造体のように、ヒープのメモリを持つ型は、コピーではなくムーブされます。

関数が値を見るだけでいいときは、これを選びましょう。ほとんどの場合、これが正しい直し方です。引数を参照に変えて、&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 を使います。

渡した先のコードが自分専用のコピーを本当に必要としていて(たとえば構造体や 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) のほうが読みやすいことも多いです。

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 で共有します。詳しくは並行処理と共有状態を見てください。

  • E0502:値を借用したあと、その借用がまだ使われているうちに値を書き換えようとした。
  • E0499:同じ値を同時に2回、可変で借用した。
  • E0106:ライフタイムの指定がない。ムーブを避けようとして参照を返したときによく出ます。
  • E0308:型の不一致。たとえば借用に切り替えたあとで、&str が必要なところに String を渡したとき。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. report が String のとき、save(report) を呼んだあとで report を表示しようとしました。なぜ E0382 になるのですか?

2. 関数は Vec<String> を読むだけです。ふつう、いちばんよい直し方はどれですか?

3. #[derive(Clone, Copy)] を付けられる構造体はどれですか?

スコア: 0 / 3