質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.01%

構造体 S 内の &mut T なフィールド経由の更新に &mut S を要求されるのは何故か

解決済

回答 3

投稿

  • 評価
  • クリップ 3
  • VIEW 3,440

Eki

score 406

struct S<'a> { y: &'a mut i32 }

fn bar() {
    let mut x: i32 = 1;
    let s = S { y: &mut x };
    *s.y = 5;
}

fn baz() {
    let mut x: i32 = 1;
    let s = S { y: &mut x };
    let z = &s;
    *z.y = 5;
}

上のコード片で、 bar() 関数ではエラーは起きませんが、 baz() 関数では次のようなエラーが発生します。

error[E0389]: cannot assign to data in a `&` reference
  --> /home/dai/workspace/daily/2018/0222/junk00-11-41.rs:15:5
   |
15 |     *z.y = 5;
   |     ^^^^^^^^ assignment into an immutable reference

ここでコンパイラは z.y が immutable reference であるとしています。

疑問は、

  1. どこを見て z.y を immutable reference としているのか
    S::y の型だけ見れば &mut i32 なのに。
  2. 参照経由で *S::y を書き換えるには s を mutable にするしかないのか
    baz() 関数でも s を mutable にして z を &mut 参照とすればエラーはなくなりました。

以下、1点目の、なぜ immutable reference とされているかを理解するために考えたこと・試したことを記述します。少々長くなってしまいましたが、誤解している点などありましたら指摘していただけると嬉しいです。


以後、式 a が型 T であることを a : T と書きます (ライフタイムは省略) 。a が mutable であることを mut a と書きます。

まず、 S の定義では確かに y : &mut i32 と定義されています。*s.y は y そのものに対する変更 (y が参照する先を変更するなど) ではなく y の参照先を書き換える行為なので、 s が mut s である必要はないと考えました。実際 foo() 関数によりこれは確かめることができています。

*s.y = 5; という処理で s の所有権がそのまま絡むような操作はないと思っています。なので s への参照 z を経由しても同じことができてほしいです。かつ、 mut s が要求されていないところでもし z : &mut S が要求されるとすると違和感があるので、 z : &S で通るはずだと考えます。ところが実際エラーが出ます。

適当に mut を付けて次の qux() 関数のように書き換えると、コンパイルが通るようになりました。

fn qux() {
    let mut x: i32 = 1;
    let mut s = S { y: &mut x }; // mut s に
    let z = &mut s; // &mut 参照に
    *z.y = 5;
}

つまり z : &mut S が要求されています。そして s を &mut 借用するために mut s も要求されます。これはもしかして、 &mut な借用は同時に一つだけ存在するというルールに違反しないためにこのようになっているのでしょうか。確かに z : &S 経由で s.y : &mut i32 にアクセスできてしまうと、 s の immutable な参照ならいくつでも作れてしまうので、 S::y という &mut な参照が複数作成できることになります。

もし S::y : &mut i32 が、 a : S または a : &mut S のときのみ S::y : &mut i32 で、 a : &S のときは S::y : &i32 となると見做されるなら、確かに先のように複数作成できてしまうということはなくなります。

この仕組みを考えていたところで、次の関数 quux() を試してみました。

fn quux() {
    let mut x: i32 = 1;
    let mut s = S { y: &mut x };
    let z = &mut s;
    *s.y = 5; // z -> s に変更
}

すると、エラーが発生して :

error[E0506]: cannot assign to `*s.y` because it is borrowed
  --> /home/dai/workspace/daily/2018/0222/junk00-11-41.rs:29:5
   |
28 |     let z = &mut s;
   |                  - borrow of `*s.y` occurs here
29 |     *s.y = 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 となると見做される

仕組みはこういうことなのでしょうか。

非常に長くなりましたが、よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+9

もし 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 への矢印は実際にはポインタではありませんが、これはある種の門のような役割を果たしています。 T への参照を取得するには RefCell の排他制御を通過する必要があり、同時に書き込みをしようとするとパニック(実行時エラー)になります。

これにより、Rustのエイリアス規則を破ることなく、複数の箇所から交互に書き換えを行うことができます。

 例外2: ローカル変数のmut

ローカル変数の mut は上に述べたような「排他/共有」の原則よりも、「可変/不変」という文字通りの意味に近い動作をします。そもそもローカル変数は常に排他的に扱えるものなので、排他/共有の区別は意味がありません。そのためおおよそ以下のような動作をします。

  • そのローカル変数自体への &mut 参照をとるには、 let mut が必要。
  • let x : &mut T の場合は、その &mut 参照はそのまま取れる。

つまり、 let mut は「そのローカル変数自体を書き換えられる」という状況を可能な限り再現する規則になっています。しかし、 let x : Cell<T> とすれば x 自体を書き換えられるので、あくまで「可能な限り再現する」という言い方が妥当だと思います。(また逆に、 let x : Box<T> では T はヒープ上にあるにも関わらずimmutable扱いになります。)

 まとめ

Rustのこのような挙動は継承可変性 (inherited mutability)と呼ばれます。これは &T/&mut T を単に「可変」「不変」として見るのではなく、「排他」「共有」と排他制御の規則の組み合わせで理解するといいと思います。これをオプトアウトする仕組みとしてRefCellなどの内部可変性 (interior mutability)があります。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/22 15:21

    丁寧な回答をありがとうございます。排他 / 共有の考え方でスッキリしました。理解を確認させてください。

    mut キーワードには参照の型としての mut と、変数の mutability の意味の mut (パターンで使われる mut) の二種類がある。これらは別物と考え、必ずしも対応しているわけではない --- z : &mut T に対して *z は mut 変数と全く同じではない (これは当初の誤解です) 。 &T だろうが &mut T だろうがデリファレンスした瞬間に T になるので、これが書き換えられるかどうかは別のチェックになる。

    参照の型としての mut には「排他」の意味がある。&mut は対象を排他的に参照する。参照を通して安全に参照先を書き換えるには、その参照先を指す参照が唯一であることが必要。 &&T, &&mut T, &mut &T は共有参照の段階で他にも参照が存在しうるので (分かりやすい図をありがとうございます) 、対象となる T を排他的に参照していると保証できない。 &mut &mut T なら排他性を保証できるから、書き換えられる。

    変数の mutability の意味の mut は、単に直接的な代入や &mut 参照ができる変数であることを示すのみ (そのローカル変数が可変であることとは必ずしも同値ではない : 例外2, Cell) 。

    (しかし参照が唯一であることを静的に保証する継承可変性は制約が強烈なので、内部可変性が導入された。 Cell はコピーで実装されるので排他性は問題にならない。 RefCell は (UnsafeCell でポインタ演算を通して) 一旦コンパイラのチェックを無効化し、チェックを (BorrowRef(Mut)::new 等で自力実装して) 実行時に行い、参照を与える。)

    総合すると、 mut s でなくても *s.y = 5; がエラーにならないのは、

    値の所有権を持つ存在は唯一であり、かつ、参照されている間は所有権を持つ存在でも書き換えができないため、「s.y という &mut 参照の排他性」に影響を及ぼさない

    から。しかし、 *z.y = 5; がエラーになるのは、

    z : &S, z.y : &mut i32 であり、 &S が複数存在し得るため、 z.y に対する参照が唯一であると保証できないから。「参照が唯一であると保証するため」に z : &mut S が必要。

    そして、 mut s については、

    実際の *z.y = 5; 操作自体に s が可変である必要はないが、s の &mut 参照を得る際には必要になる

    ため。

    このような考え方で正しいでしょうか?

    キャンセル

  • 2018/02/22 16:15

    はい。それで良いと思います。

    キャンセル

  • 2018/02/22 17:01

    理解できました。お付き合い頂き、ありがとうございました。

    キャンセル

0

もし S::y : &mut i32 が、 a : S または a : &mut S のときのみ S::y : &mut i32 で、 a : &S のときは S::y : &i32 となると見做される 

私もそうだと思います。struct 全体またはフィールドを借用する際、フィールドが mutable になるかは、struct のフィールド定義(y: &mut i32)ではなく、struct 自体の mutability で決まるようです。

たとえば以下のように s が immutable の時に y フィールドの mutable な参照を作ろうとしてもコンパイルできません。

fn baz2() {
    let mut x = 1;
    let mut v = 5;
    let s = S { y: &mut x };
    let z_y = &mut s.y;       // y フィールドだけ mut 借用
    *z_y = &mut v;
}
error[E0596]: cannot borrow field `s.y` of immutable binding as mutable
  --> src/main.rs:13:20
   |
12 |     let s = S { y: &mut x };
   |         - consider changing this to `mut s`
13 |     let z_y = &mut s.y;
   |                    ^^^ cannot mutably borrow field of immutable binding

s を mut にするとコンパイルできるようになります。

理由はおっしゃってる通りだと思います。

確かに z : &S 経由で s.y : &mut i32 にアクセスできてしまうと、 s の immutable な参照ならいくつでも作れてしまうので、 S::y という &mut な参照が複数作成できることになります。

たとえば S に fn f(&self) というメソッドを実装したとき、self.y が変更できてしまうと問題があります。

1. どこを見て z.y を immutable reference としているのか S::y の型だけ見れば &mut i32 なのに。

let z = &s により、s.y が immutable reference になっています。

2. 参照経由で *S::y を書き換えるには s を mutable にするしかないのか

そうなります。

もしどうしても s が immutable reference の時に s.y を書き換えたかったら、コンパイル時のチェックをあきらめて、Cell などによる interior mutability を使う方法もあります(最後の手段です)

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/22 12:08

    丁寧に回答いただき、ありがとうございます。
    mut s でない場合、 z_y = &mut s.y; というのが通らないことは納得できます。これは s.y を &mut 借用しようとしているのに s.y は immutable だからで、おっしゃる通り構造体フィールドの mutability は親のものを継承するというルールです。つまり、 s が immutable のときは s.y も immutable だから、 s.y を変更することはできないということですよね。
    しかし、疑問なのは、 s.y を変更しようとしているのではなく *s.y (s.y の参照先) を変更しようとしている時にもエラーになることなのです。通常 &mut 参照先の値を変更するのに参照を束縛してある変数が mutable である必要はないはずです。実際、次のように。

    let mut x: i32 = 1;
    let y = &mut x; // y は immutable
    *y = 5; // OK

    キャンセル

  • 2018/02/22 12:15

    親が参照というのがポイントだと思います。&s であることがフィールドに伝播して & &mut s.y として扱われるのではないでしょうか。

    キャンセル

  • 2018/02/22 12:38

    &s であることがフィールドに伝播...伝播するのは mutability だけではないということでしょうか?

    キャンセル

0

こんにちは。先の回答に補足しますね。エラーメッセージについて。

やや意外かもしれませんが、&&mut T から &mut T は作れませんが、 &T は作れます。

struct S<'a> {
    s: &'a mut String,
}

fn main() {
    let s = &mut "str".to_string();
    let t = S { s };
    let t = &t;

    //  // cannot borrow data mutably in a `&` reference (assignment into an immutable reference) [E0389]
    // let s: &mut String = t.s;

    // こっちはOK
    let s: &String = t.s;
}

おっしゃるとおり参照はコピー可能なので &&mut T から &mut T が作れてしまうと1つしか存在できないはずの&mut T をいくらでもコピーできてしまいますね。
そして &mut T は &T のサブタイプのようなものですからキャストできるのは理解できるかと思います。

1, 2の疑問ともにこれ由来のエラーメッセージかと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/22 12:29

    !!なるほど。ということは、 &&mut T と &&T は実質同じもの、ということでしょうか?

    キャンセル

  • 2018/02/22 12:41

    fn grault() {
    let mut x: i32;
    let s = S { y: &mut x };
    let z = &s;
    *(*z).y = 5; // 明示的にデリファレンスすれば *z : S のはずなので foo() と同じ状況?
    }

    と思ったのですが、これもエラー (cannot assign to data in a `&` reference) になります。これがエラーになるべき理由は &mut T をコピーできてしまうから、ということでしょうが、型システム上は何で弾かれているのでしょうか?

    キャンセル

  • 2018/02/22 12:50

    借用が正当かどうかはborrowckという型検査よりも後のフェーズで検査されているので単純なシンタックスでは説明がつかないかと。

    キャンセル

  • 2018/02/22 15:35

    確かに、 *z.y : i32 な時点で、 *z.y = 5; は型上は何かの i32 に 5 を代入する操作でしかないわけですよね。

    これが書き換えられるかどうかは別のチェックになる、と。例えば i32 が変数なら mut 変数かどうか。例えば参照なら継承可変性として mutable かどうか --- この辺りが borrowck に当たる、ということなのですね。

    改めて Rust の mut は型ではなく変数の属性であることを肝に銘じます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.01%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる