mox692 のブログ

妄想の書き留め場所.

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 because self.a has type A, which does not implement the Copy 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つ丁寧に処理を追っていくと割と状況を掴みやすい気がしてます.

/* -----codeの行番号----- */