質問: 5; | ^^^^^^^^ assignment to borrowed `*s.y` occurs here ``` ...つまり、エラーによると `&mut s` (`&s` も同様に) は `s.y` のみならず `*s.y` まで借用しているということになると思いますが、その認識で正しいでしょうか。それならば `&s` は `*s.y` を `&*s.y` のような形で借用し、`&mut s` は `*s.y` を `&mut *s.y` のような形で借用しているということなのでしょうか。 先の > > もし `S::y : &mut i32` が、 `a : S` または `a : &mut S` のときのみ `S::y : &mut i32` で、 `a : &S` のときは `S::y : &i32` となると見做される > > > 仕組みはこういうことなのでしょうか。 非常に長くなりましたが、よろしくお願いします。
回答: > > もし `S::y : &mut i32` が、 `a : S` または `a : &mut S` のときのみ `S::y : &mut i32` で、 `a : &S` のときは `S::y : &i32` となると見做される > > > これはその通りです。「フィールドが可変(`&mut T` や `T`)であること」と「構造体自体が可変(`&mut S` や `S`)であること」の両方が必要です。 対応方法は状況により主に2通りあります。 * 「その関数だけがそのデータを書き換える」という状況であれば、参照を `&mut S` にすればよいです。 * 「他の関数とデータを共有しながら書き換える」という状況であれば、`RefCell` や `Mutex` などの内部可変性 (interior mutability) を用います。 以下、なぜこのような挙動になるのかを説明します。 原則1: 継承可変性 =========== Rustのこのような挙動は**継承可変性 (inherited mutability)** といいます。フィールドにアクセスするまでの経路が全て `mut` であるときだけ可変とみなされるというものです。 ところで、Rustのドキュメンテーションをよく読むと、 `&T` のことはimmutable referenceではなく**shared** referenceと呼んでいる場合があります。例えばAPIドキュメントには > > References, both shared and mutable. > > > と書いてあります。このようにRustの `&mut T`/`&T` には「可変/不変」のほかに、「排他/**共有**」という見方があります。この考え方を使うと、 `&&mut T` から `&mut T` を取り出せない理由が明確になります。 !排他と共有による継承可変性の図 上の図で矢印を参照関係とすると、 `&&T`, `&&mut T`, `&mut &T` の3つは、 `T` が他の参照と直接的あるいは間接的に共有されているのに対し、 `&mut &mut T` だけは `T` を他の参照と共有していないことがわかります。 今回のケースは構造体がかかわっているものの、 `&&mut T` のケースの亜種だといえます。 原則2: 排他制御 ========== ではこれがどうして「可変参照」「**不変**参照」とも呼ばれるのか。それは一般的な排他制御の規則(ファイルロックやデータベース、並行処理などで使われている)で説明がつきます。つまり、 * データの読み取りをするときは、同時に書き込みをしている人がいないか確認する。 * データの書き込みをするときは、同時に読み取りまたは書き込みをしている人がいないか確認する。 というものです。Rustの参照は、 * 共有参照 `&T` からは、読み取りだけができる。 * 排他参照 `&mut T` には、読み取りと書き込みができる。 という規則によって、**静的に**この排他制御を実現していることになります。これによりRustの参照はプログラムの他の部分の干渉を受けないことが保証され、コンパイラによる最適化にとって非常に良いヒントになります。 例外1: 内部可変性 =========== ここまでに述べた原則では、「プログラムの複数の部分でデータを共有しつつ交互に書き換える」ということができません。これらは**動的に**排他制御を行う仕組みによって実現されます。これらを **内部可変性** (interior mutability) といいます。 !RefCellの排他制御の図 上の図で `RefCell` から `T` への矢印は実際にはポインタではありませんが、これはある種の門のような役割を果たしています。 `T` への参照を取得するには `RefCell` の排他制御を通過する必要があり、同時に書き込みをしようとするとパニック(実行時エラー)になります。 これにより、Rustのエイリアス規則を破ることなく、複数の箇所から交互に書き換えを行うことができます。 例外2: ローカル変数の`mut` ================== ローカル変数の `mut` は上に述べたような「排他/共有」の原則よりも、「可変/不変」という文字通りの意味に近い動作をします。そもそもローカル変数は常に排他的に扱えるものなので、排他/共有の区別は意味がありません。そのためおおよそ以下のような動作をします。 * そのローカル変数自体への `&mut` 参照をとるには、 `let mut` が必要。 * `let x : &mut T` の場合は、その `&mut` 参照はそのまま取れる。 つまり、 `let mut` は「そのローカル変数自体を書き換えられる」という状況を可能な限り再現する規則になっています。しかし、 `let x : Cell` とすれば `x` 自体を書き換えられるので、あくまで「可能な限り再現する」という言い方が妥当だと思います。(また逆に、 `let x : Box` では `T` はヒープ上にあるにも関わらずimmutable扱いになります。) まとめ ==== Rustのこのような挙動は**継承可変性 (inherited mutability)**と呼ばれます。これは `&T`/`&mut T` を単に「可変」「不変」として見るのではなく、「排他」「共有」と排他制御の規則の組み合わせで理解するといいと思います。これをオプトアウトする仕組みとして`RefCell`などの**内部可変性 (interior mutability)**があります。