(A) においては、rect の内容が変更されないから mut は不要。
right_bottom の参照先は書き換わるけれど、right_bottom を指すアドレス自体は変更されないため。
その考え方で合っています。
(B) においては、func でキャプチャされる変数が、rect だけでなく rb にも及ぶ。(lt はキャプチャされない)
そのためクロージャ生成時に、rect と rb を含む構造体が生成されて、move_x() 実行時に rb が変更されると判断されて、func に mut が必要となる。
なんとなく合っているような気がしますが、できるだけ正確に説明してみます。
funcにmutが必要になる直接的な理由は、func();のところでFnMutのcall_mut(&mut self, ..)メソッドが使われるからです。&mut selfを得るためにはself(=func)がmutでなければなりません。これは&mut rbを得るためにlet mut rbにするのと同じ理由です。
ちなみに他のFnOnceとFnのcall..(..)メソッドでは、順にself、&selfを引数に取るためfuncのmutを付ける必要はありません。
クロージャが定義されたときにFnMutなどのどのトレイトが実装されるかは、以下の優先順位で決定されるようです。
Fn、FnMut、FnOnceの全てを実装する
FnMutとFnOnceを実装する
FnOnceだけを実装する
クロージャをコールするときにどのトレイトメソッドが使われるかは、コード側で指定(制約)がなければ以下の優先順位で決定されるようです。
Fnを実装しているならcall(&self, ..)
FnMutを実装しているならcall_mut(&mut self, ..)
FnOnceのcall_once(self, ..)
コード側の指定というのは、たとえば 別の質問の回答で書いた ように、impl FnOnce()を引数に取る関数にクロージャを渡したときのようなことを指します。
ご質問のコードでは使用するトレイトメソッドの指定はされておらず、またfuncにmutが必要なことからcall_mut(&mut self, ..)が使われていることがわかります。ですから、そのクロージャはFnMutとFnOnceを実装していると考えられます。(Fnは実装されていない)
Fnが実装されていない理由ですが、move_x()が&mut self(ここでのselfはPoint)を要求するからです。Fnが要求するメソッドはcall(&self, ..)ですが、&をとおして&mutを得ることができず、従ってFnを実装できません。&をとおして&mutを得られないことはご質問のコードに以下のコードを追加することで確認できます。
rust
1 // Rectangleの不変借用
2 let rect1 = ▭
3 rect1.right_bottom.move_x(); // コンパイルエラーになる
エラーの内容は以下のとおりです。
console
1error[E0596]: cannot borrow `*rect1.right_bottom` as mutable, as it is behind a `&` reference
2 --> src/bin/main1.rs:30:5
3 |
429 | let rect1 = ▭
5 | ----- help: consider changing this to be a mutable reference: `&mut rect`
630 | rect1.right_bottom.move_x();
7 | ^^^^^^^^^^^^^^^^^^ `rect1` is a `&` reference, so the data it refers to cannot be borrowed as mutable
最後にキャプチャされる変数ですが、
func でキャプチャされる変数が、rect だけでなく rb にも及ぶ。(lt はキャプチャされない)
今回のケースでは以下の2種類のどちらかでキャプチャされると考えられます。
&mut rectをキャプチャする。(クロージャ内ではleft_topフィールドとright_bottomフィールドにアクセスできる)
&mut rect.right_bottomをキャプチャする。(クロージャ内ではright_bottomフィールドだけにアクセスできる)
実際のコードではright_bottomフィールドだけにアクセスするので後者が好ましく思えますが、こちらのissue を読んだ感じでは、現在のRustバージョンでは前者になっているようです。そのissueは前者から後者に変更するためのものですが、設計面で考慮することがいろいろとあり、まだ実現されてないようです。
今までの話をまとめますと、以下のようになります。
funcにmutが必要になる直接的な理由は、クロージャをコールするときにFnMutのcall_mut(&mut self, ..)メソッドが使われるから
call_mut(&mut self, ..)が使われる理由は、クロージャがFnMutとFnOnceを実装しており、Fnは実装していないから
- クロージャが
Fnを実装できない理由はmove_x()メソッドが&mutを要求するから
最後に、余談ですが、2のところはコードで指定することでcall_mut(&mut self, ..)ではなく、call_once(self, ..)を使うことも可能です。
rust
1 // funcを「FnOnceを実装したトレイトオブジェクト」へと型強制することで
2 // `call_once()`を使うように仕向けられる。これによりfuncのmutは不要になるが
3 // 1回しか呼べなくなる
4 let func: Box<dyn FnOnce()> = Box::new(|| {
5 rect.right_bottom.move_x();
6 });
7 func();
8 func(); // FnOnceなので2回目のコールはできない。コンパイルエラーになる
2021/05/03 06:03