Posted at

"cannot move out of captured outer variable in an `FnMut` closure"が解消できないときは

More than 1 year has passed since last update.


環境

$ rustc --version

rustc 1.16.0 (30cf806ef 2017-03-10)


本題

次のコードをコンパイルします。

use std::thread;

fn main() {
let things = vec![1,2,3,4];
let mut buf = vec![];
let handle_thread: Vec<_> = things.iter().map(|&d| {
thread::spawn(move || {
do_thing(&mut buf);
})
}).collect();
}

fn do_thing(x: &mut [u8]) {}

(https://github.com/rust-lang/rust/issues/31752 より引用 )

すると、次のようなエラーが出力されます。

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure

--> src/main.rs:7:43
|
7 | thread::spawn(move || {
| ^^^^^^^ cannot move out of captured outer variable in an `FnMut` closure

問題点を示してくれているのですぐに直りそうですが、実は、上記の出力だけを眺めていても永遠に解決できません。


解消のために

エラーメッセージを訳すと「FnMutクロージャでキャプチャした外部変数をムーブアウトすることはできません」となります。

FnMutクロージャとは何を指しているのかというと、map()の引数です。

つまりこのエラーは、map()の引数の中で外部変数が失われてしまっては反復できないことを意味しています。

map()の引数の外部変数はbufのみです。

ではどこでbufをムーブアウトしているかというと、spawn()の引数であるmoveクロージャ内で参照するだけでムーブアウトしたことになります。VecCopyを実装していないので、参照するだけで所有権がmoveクロージャに移ってしまいます。

他にムーブアウトする例としては、所有権を移動する関数に渡すというのが挙げられます。

use std::thread;

fn main() {
let things = vec![1,2,3,4];
let mut buf: Vec<u8> = vec![];
let handle_thread: Vec<_> = things.iter().map(|&d| {
do_thing(buf)
}).collect();
}

fn do_thing(x: Vec<u8>) {}

この場合、エラーは以下のようになります。

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure

--> <anon>:7:18
|
7 | do_thing(buf)
| ^^^ cannot move out of captured outer variable in an `FnMut` closure

error: aborting due to previous error

これはかなりわかりやすいのですが、moveクロージャの場合はクロージャの開始位置しか教えてくれないので、どの変数が問題なのかわかりにくくなっています。

なお解消法ですが、例えばclone()を使う方法が挙げられます。

use std::thread;

fn main() {
let things = vec![1,2,3,4];
let buf = vec![];
let handle_thread: Vec<_> = things.iter().map(|&d| {
let mut buf = buf.clone();
thread::spawn(move || {
do_thing(&mut buf);
})
}).collect();
}

fn do_thing(x: &mut [u8]) {}

このコードは、そもそもエラーを示すための例なので無意味ですが、こうして外部変数の消失を避けることができます。


今後の展望

コードを引用した https://github.com/rust-lang/rust/issues/31752 によると、どの変数が問題なのか示すようにしたPRがmasterにマージされたようです。