TL;DR
別の変数や関数にムーブされた後の値を使おうとしています。Rustの所有権システムは、ムーブ後に元のバインディングを無効化します。2つの簡単な修正方法:ムーブ前に.clone()を呼ぶか、値そのものの代わりに参照(&value)を渡すかです。
// エラーのコード
let s = String::from("hello");
let s2 = s; // ここでsがムーブされる
println!("{}", s); // error[E0382]: use of moved value: `s`
// 修正1: clone
let s = String::from("hello");
let s2 = s.clone();
println!("{}", s); // OK
// 修正2: 借用
let s = String::from("hello");
let s2 = &s;
println!("{}", s); // OK
実際に何が起きているか
Rustにおけるヒープに確保された値は、常にただ1つの所有者を持ちます。s2 = sと代入したり、sを関数に渡したりすると、所有権が移転します。Rustではこれをムーブと呼びます。ムーブ後、sは消滅します。コンパイラはゴーストの使用を許しません。
エラーの全体像はこのようになります:
error[E0382]: use of moved value: `s`
--> src/main.rs:4:20
|
2 | let s2 = s;
| - value moved here
3 | println!("{}", s);
| ^ value used here after move
|
= note: move occurs because `s` has type `String`, which does not implement the `Copy` trait
最後の行が重要です。Copyを実装している型——整数、真偽値、文字、Copy型のタプルなど——は、ムーブではなく暗黙的にコピーされます。String、Vec、HashMap、そして大半のヒープ型はCopyを実装していないため、ムーブになります。
よくあるシナリオと修正方法
シナリオ1:関数へのムーブ
fn save_to_db(name: String) { /* nameを消費する */ }
let name = String::from("Alice");
save_to_db(name); // nameがsave_to_dbにムーブされる
println!("{}", name); // error[E0382]: use of moved value: `name`
修正A: 所有権を持つ代わりに借用するよう関数シグネチャを変更する:
fn save_to_db(name: &str) { /* nameを借用する */ }
save_to_db(&name);
println!("{}", name); // OK
修正B: 関数を変更できない場合は、渡す前にcloneする:
save_to_db(name.clone());
println!("{}", name); // OK
シナリオ2:ループ内でのムーブ
深夜2時によく踏む罠です:
let data = vec![1, 2, 3];
for _ in 0..3 {
process(data); // error[E0382]: use of moved value: `data`
// 最初のイテレーションでムーブされ、以降は消滅
}
fn process(v: Vec<i32>) { /* vを消費する */ }
修正A: 関数が所有権を必要としない場合は参照を渡す:
fn process(v: &[i32]) { /* vを借用する */ }
for _ in 0..3 {
process(&data); // OK
}
修正B: 関数が値の所有権を必要とする場合のみ、ループ内でcloneする:
for _ in 0..3 {
process(data.clone());
}
シナリオ3:クロージャへのムーブ
let config = Config::new();
let handler = move || {
println!("{:?}", config); // configがクロージャにムーブされる
};
println!("{:?}", config); // error[E0382]: use of moved value
修正: moveクロージャにキャプチャされる前にcloneして、それぞれが独自のコピーを持つようにする:
let config = Config::new();
let config_clone = config.clone();
let handler = move || {
println!("{:?}", config_clone);
};
println!("{:?}", config); // OK
シナリオ4:構造体フィールドの部分ムーブ
struct User {
name: String,
email: String,
}
let user = User {
name: String::from("Alice"),
email: String::from("alice@example.com"),
};
let name = user.name; // 部分ムーブ!
println!("{}", user.email); // OK — emailはムーブされていない
println!("{}", user.name); // error[E0382]: partial move
修正: 構造体を破壊しないようフィールドをcloneする:
let name = user.name.clone();
println!("{}", user.name); // OK
シナリオ5:matchによる値の消費
let opt = Some(String::from("data"));
match opt {
Some(s) => println!("Got: {}", s), // sがoptからムーブされる
None => {}
}
println!("{:?}", opt); // error[E0382]
修正: 参照にマッチさせる——optの前に&を追加する:
match &opt {
Some(s) => println!("Got: {}", s),
None => {}
}
println!("{:?}", opt); // OK
clone()がコストに見合わない場合
ホットループで100MBのVecをcloneするとパフォーマンスが著しく低下します。.clone()に頼る前に、以下の代替手段を検討してください:
- Arc — スレッド間での共有所有権。cloneしてもアトミックカウンタをインクリメントするだけで、データはコピーされません。
- Rc — 同じ考え方で、シングルスレッド専用。
- Cow — 変更が必要になるまで借用し、その時点で遅延cloneします。
use std::sync::Arc;
let data = Arc::new(vec![1, 2, 3]);
let data2 = Arc::clone(&data); // ポインタの安価なclone、データのコピーではない
std::thread::spawn(move || {
println!("{:?}", data2);
});
println!("{:?}", data); // OK
確認方法
修正を適用したら、E0382が解消されているか確認します:
cargo build 2>&1 | grep "E0382"
出力がなければ問題ありません。完全にクリーンな再ビルドを行う場合:
cargo clean && cargo build
Clippyも実行してみてください——手動cloneより慣用的な解決策を提案してくれることがよくあります:
cargo clippy
判断ガイド
- 関数が値を読むだけの場合 →
&Tを渡す - 関数が値を変更する必要がある場合 →
&mut Tを渡す - 関数が所有権を必要とし、かつ元の値も使いたい場合 →
.clone() - スレッド間で値を共有する場合 →
Arc<T> - シングルスレッドのコードで値を共有する場合 →
Rc<T> - 型が小さくスタックのみに存在する場合 →
Copyを導出する
#[derive(Copy, Clone)]
struct Point { x: f64, y: f64 }
let p = Point { x: 1.0, y: 2.0 };
let p2 = p; // ムーブではなくコピー
println!("{}", p.x); // OK

