何が起きたのか
Rustプログラムをコンパイルしていると、突然このエラーが表示されます:
error[E0382]: use of moved value: `name`
--> src/main.rs:6:20
|
4 | let name = String::from("Alice");
5 | greet(name);
| ---- value moved here
6 | println!("{}", name);
| ^^^^ value used here after move
Rustの所有権モデルがここで機能しています。nameをgreet()に渡すと、その関数に所有権が移動します。そうなると、nameは呼び出し元のスコープから消えてしまい、コンパイラはもはや所有していない変数を触ることを許可しません。
必ずしもロジックエラーではありません。コンパイラはメモリ安全性を強制しているのであり、それがRustの本質です。適切な修正方法は、呼び出し後にその値で何をしたいかによって異なります。
エラーを再現する
fn greet(name: String) {
println!("Hello, {}!", name);
}
fn main() {
let name = String::from("Alice");
greet(name); // 所有権がgreet()に移動する
println!("{}", name); // ERROR: value used after move
}
cargo buildを実行すると、値がどこで移動したか、そしてどこで再使用しようとしたかを正確に示す行番号付きの完全なE0382エラーが表示されます。
どのケースに該当するかを診断する
値を渡した後に何が必要かを答えることで、適切な修正方法を選びましょう:
- その後に値を読み取るだけでよい場合 → 参照(
&)を使用する - データの独立したコピーが必要な場合 → クローンまたはコピーする
- 関数に所有権を返してほしい場合 → 値を返す
- ループやクロージャに移動させる場合 → キャプチャ前にクローンするか、構造を変える
解決策1:値の代わりに参照を渡す
ほとんどの場合、これが正しい修正方法です。所有権を受け取る代わりに、値を借用するように関数を変更します。
fn greet(name: &str) { // Stringを所有する代わりに文字列スライスを借用する
println!("Hello, {}!", name);
}
fn main() {
let name = String::from("Alice");
greet(&name); // greet()に貸し出す
println!("{}", name); // まだ有効 — 所有権は手放していない
}
&演算子は参照を作成します。関数は一時的に値を借用し、関数が返るときにその借用は解放されます。所有権は常に手元に残ります。
現在の関数がStringを受け取っている場合、&strに切り替えることがほぼ常に正しい選択です — 関数が内部で文字列を保存または拡張する必要がない限り。
解決策2:値をクローンする
関数がデータを所有する必要があり、かつその後も元のデータを保持する必要がある場合は、クローンします:
fn greet(name: String) {
println!("Hello, {}!", name);
}
fn main() {
let name = String::from("Alice");
greet(name.clone()); // greet()に独自のコピーを渡す
println!("{}", name); // 元の値はまだ有効
}
クローンはヒープ上に新しいコピーを割り当てます。これは確実な解決策ですが、コストがかかります。ホットループ内や大きなデータ構造には避けてください。1MBのVecをイテレーションのたびにクローンすると、すぐに積み上がります。
解決策3:Copyトレイトを使用する
i32、f64、bool、charなどのプリミティブ型はCopyトレイトを実装しています。これらは移動せず、明示的なクローンなしに自動的にコピーされます。
fn double(x: i32) -> i32 {
x * 2
}
fn main() {
let n = 5;
let result = double(n);
println!("{} doubled is {}", n, result); // 問題なし、i32はCopyです
}
すべてのフィールドもCopyであれば、独自の構造体でもこの動作を実現できます:
#[derive(Copy, Clone)]
struct Point {
x: f64,
y: f64,
}
fn translate(p: Point, dx: f64) -> Point {
Point { x: p.x + dx, y: p.y }
}
fn main() {
let p = Point { x: 1.0, y: 2.0 };
let q = translate(p, 5.0);
println!("original x: {}", p.x); // 問題なし — PointはCopyです
}
注意点:String、Vec、またはその他のヒープ割り当て型をフィールドに持つ場合、Copyを導出することはできません。
解決策4:ループやクロージャへの移動
クロージャはE0382が最も意外な形で現れる場所です。moveキーワードは所有権をキャプチャし、一度キャプチャされると元の変数は消えてしまいます:
// 問題: クロージャが所有権を取得する
let msg = String::from("hello");
let print_msg = move || println!("{}", msg);
print_msg();
println!("{}", msg); // ERROR: moved into closure
// 修正: クロージャがキャプチャする前にクローンする
let msg = String::from("hello");
let msg_clone = msg.clone();
let print_msg = move || println!("{}", msg_clone);
print_msg();
println!("{}", msg); // 元の値はまだここにある
通常のforループ内では、この問題は発生しにくいです。println!マクロは暗黙的に借用するため、msgを参照しながら&itemsをイテレートしても、クローンなしで動作します。
解決策5:所有権を返す
関数が一時的に所有権を必要とし、完了後に返してほしい場合:
fn process(data: String) -> String {
println!("Processing: {}", data);
data // 所有権を呼び出し元に返す
}
fn main() {
let s = String::from("some data");
let s = process(s); // 返り値で再バインドする
println!("Got back: {}", s);
}
借用はほぼ常により簡潔ですが、このパターンは関数が値を変換または拡張する場合 — ビルダーや文字列フォーマッターなど — に有効です。
修正を確認する
再ビルドして出力を確認します:
cargo build
E0382エラーがゼロになれば、所有権の問題は解決されています。さらに一歩進んで、修正がセマンティック的に正しいことを確認しましょう:
cargo test
cargo clippy # さらに簡略化できるパターンを検出する
Clippyが関数シグネチャにフラグを立て、Stringの代わりに&strを提案している場合、それは借用で十分なところでクローンしているサインです。
クイック判断ガイド
- 関数がデータを読み取るだけ → パラメータを
&Tまたは&strに変更する - 関数がデータを保存する(例:構造体フィールドに)→ 所有権の移動は意図的なので、そのままにする
- 元のデータと変更されたコピーの両方が必要 → 渡す前に
.clone()する - データがプリミティブまたはスタックのみの構造体 →
Copyを導出するか、それに依存する moveクロージャ内で使用される → クロージャがキャプチャする前に値をクローンする
覚えておくべきこと
E0382はコンパイラが細かいことを言っているのではなく、リリース前に実際のバグを捕捉しているのです。C++で同じパターンを書くとコンパイルは通りますが、実行時にuse-after-freeが発生します。Rustはコンパイル時にそれを表面化し、行番号と明確なメッセージを提供します。
まず借用から始めましょう。fn foo(s: String)をfn foo(s: &str)に変更するのが最初のステップです — より柔軟で、メモリ割り当てをスキップし、ほとんどのケースでエラーを解決します。参照を検討した後でのみ、.clone()に手を伸ばしてください。

