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

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

新規登録して質問してみよう
ただいま回答率
87.20%
Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

解決済

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

Eki
Eki

総合スコア429

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

3回答

2評価

3クリップ

6675閲覧

投稿2018/02/21 17:00

rust

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.yimmutable reference であるとしています。

疑問は、

  1. どこを見て z.yimmutable 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.yy そのものに対する変更 (y が参照する先を変更するなど) ではなく y の参照先を書き換える行為なので、 smut s である必要はないと考えました。実際 foo() 関数によりこれは確かめることができています。

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

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

rust

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() を試してみました。

rust

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ページの「注目」タブのフィードに表示されやすくなります。

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

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

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

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

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

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

まだ回答がついていません

会員登録して回答してみよう

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

ただいまの回答率
87.20%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問

同じタグがついた質問を見る

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。