(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