Rustのちょっとわかりづらいmove error
突然ですが、
struct A {} impl A { fn foo(self) -> Self{ Self{} } } struct B { a: A } impl B { fn new(a:A) ->Self { Self {a: a} } fn baz(&self) { let _ = self.a.foo(); } }
このコードは L15で次のようなコンパイルエラーを吐きます.
cannot move out of
self.a
which is behind a shared reference move occurs becauseself.a
has typeA
, which does not implement theCopy
trait
aがmoveしてしまってるぞ!的なErrorですが、一瞬みただけだとこのerrorの原因ってちょっとわかりづらくないないですか. (慣れてる人はすぐわかると思いますが自分は初めてみた時少し理解に時間がかかりました.)
errorが発生してる baz()
のコードを紐解いて、少し状況を整理してみましょう.
fn baz(&self) { let _ = self.a.foo(); }
このコードは、下記のように書き換え可能です.
fn baz(&self) { let _a = self.a let _ = _a.foo(); }
さらに、メソッドfoo
fn foo(self) -> Self{ Self{} }
は、(通常の)関数
fn foo_fn(s: A) -> A{ A{} }
と等しいことから、baz()
は
fn baz(&self) { let _a = self.a let _ = foo_fn(_a); }
こんな感じの関数と等価と言えそうです. このような形に変形してやると、foo_fnが_a
をOwnしてる様がわかりやすくなると思います.
さらにいきましょう. このbazも(methodではなく、)普通の関数と見立ててしまって、
fn baz_fn(b: &B) { let _a = b.a let _ = foo_fn(_a); }
とかけそうです. ここまで来るとerrorになる原因がかなりわかりやすくなったでしょう. そうです、他所でOwnされてるBの、メンバであるaを、foo_fnで move させてしまってますね. Bは他所で生きているにもかかわらず、Bが所有してるメンバ a がdropされてしまうために、コンパイラはerrorを吐いていたのです.
まとめ
methodでチェーンしてたりすル部分で所有権エラーが発生すると一瞬原因がわかりづらくなったりしますが、こんな感じで1つ1つ丁寧に処理を追っていくと割と状況を掴みやすい気がしてます.