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

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

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

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

Q&A

解決済

2回答

1234閲覧

mutable関連のコンパイルエラーの原因が分からない

rim_yamamoto

総合スコア22

Rust

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

0グッド

1クリップ

投稿2022/12/13 06:43

編集2022/12/14 09:30

前提

Rustを始めたのですが、mutable関連のコンパイルエラーが発生して詰まってしまったので、詳しい人に原因を教えてもらいたいです。

実現したいこと

  • コンパイルエラーの原因を知る

発生している問題・エラーメッセージ

error[E0502]: cannot borrow `self.item` as mutable because it is also borrowed as immutable --> src/sandbox.rs:22:13 | 20 | player.play(); | ------------- immutable borrow later used here 21 | 22 | self.item.change(); | ^^^^^^^^^^^^^^^^^^ mutable borrow occurs here ... 25 | player = self.get_next_player(); | ---------------------- immutable borrow occurs here

該当のソースコード

問題が発生する箇所から、余計なものを取り除き、エラーが起きる最小限のコードにしたものです。
(最小限にしたので無限ループになってしまいましたが、本来はbreakする箇所があります)

Rust

1struct Item {} 2impl Item { 3 fn change(&mut self) {} 4} 5 6struct Player {} 7impl Player { 8 fn play(&self) {} 9} 10 11struct Sandbox { 12 item: Item, 13 player: Player, 14} 15impl Sandbox { 16 pub fn start(&mut self) { 17 let mut player = &self.player; 18 19 while true { 20 player.play(); 21 22 // ここでコンパイルエラーが発生する 23 self.item.change(); 24 25 if true { 26 player = self.get_next_player(); 27 } 28 } 29 } 30 31 fn get_next_player(&self) -> &Player { 32 &self.player 33 } 34}

試したこと

エラーメッセージを和訳するとself.itemはimuutableとしても借用されているので、mutableとして借用できないとのことですが、self.itemが使われている箇所は1箇所しかなく、意味が分かりませんでした。

また、以下のようにコードに色々手を加えるとエラーが起きなくなるのですが、いずれも何故そうなるのか分かりませんでした。

以下の行どれか1つを消す

  • player.play();
  • self.item.change();
  • player = self.get_next_player();

なぜかこの3つが全て揃ってないとエラーになりません。

get_next_player() を消す

self.get_next_player() をやめて、代わりに&self.playerにするとエラーが起きません。

if 文を消す

if文を消してplayer = self.get_next_player();を外に出すとエラーが起きません。

playerへの代入を止める

player = を消して、self.get_next_player(); だけにするとエラーが起きません。

追記1

その後、思い当たった仮説を追記。

そももの原因は、self.get_next_player()のところで、selfがimmutable借用された後、self.item.change()でmuttable借用されるから?

player.play()、player = self.get_next_player()を書き換えたときエラーが起きなくなるのは、Non-lexical lifetimesの機能によって、コンパイラーがplayerにselfが借用されてないと判定されている?

ただ、if文を消すとエラーにならない原因は分からない。

補足情報(FW/ツールのバージョンなど)

rustc 1.65.0 (897e37553 2022-11-02)

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

自己レスですが、if trueを消すとエラーが出なくなる点については、コンパイラーによって、以下のように判定されているのではないかと思います。

Rust

1// 1. まず、ここでselfがplayer変数にimmutable借用される 2let mut player = &self.player; 3 4while true { 5 player.play(); 6 7 // 2. 次にここでselfがmutable借用されるが、既に1でimmutable借用されているのでエラーになる 8 self.item.change(); 9 10 // 3. ここでplayerが上書きされると、以降1のplayer参照は使われないので、2のmutable借用が問題なくなる。 11 // そのためif文がないとエラーにならないが、if文があると常に上書きされないとみなされてエラーになる 12 if true { 13 player = self.get_next_player(); 14 } 15 16 // 仮に上のifブロックに入らなかったとすると、次のループで1のplayer参照が使われるので、2は不味いということになる 17}

投稿2022/12/13 15:25

編集2022/12/14 00:30
rim_yamamoto

総合スコア22

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

同じような現象を再現する更に簡略化した例を作りました。

rust

1struct Foo { 2 x: u32, 3 y: u32 4} 5 6impl Foo { 7 fn new(x: u32, y: u32) -> Foo { 8 Foo { x,y } 9 } 10 11 fn x_ref(&self) -> &u32 { 12 &self.x 13 } 14} 15 16fn main() { 17 let mut a = Foo::new(1,2); 18 let b = a.x_ref(); 19 let c = &mut a.y; 20 println!("{}{}", b,c); 21}

同じようなエラーが出ます。

error[E0502]: cannot borrow `a.y` as mutable because it is also borrowed as immutable --> prog.rs:19:13 | 18 | let b = a.x_ref(); | - immutable borrow occurs here 19 | let c = &mut a.y; | ^^^^^^^^ mutable borrow occurs here 20 | println!("{}{}", b,c); | - immutable borrow later used here

このとき

rust

1fn x_ref(&self) -> &u32 { 2 &self.x 3}

にはライフタイムの指定を書いておりませんがこれはよくあるパターンは自動的に補われるようになっているからです。 実際には

rust

1fn x_ref<'a>(&'a self) -> &'a u32 { 2 &self.x 3}

のように解釈されています。 この関数 x_ref の入出力の間の依存関係が出来上がり、出力結果である参照が存在する限り &self としてオブジェクト全体も借用している状態です。


そしてオブジェクト全体が借用されているとき、そのオブジェクトのメンバの借用にも影響が出ます。 (逆にメンバの借用がそれを含むオブジェクトの借用に影響することもあります。) オブジェクトの mut 参照が制限されているときにメンバの mut 参照が許されるのだと整合性が取れないというのは感覚的にもわかるかと思います。

ここで a.y 自体は借用されていませんけども a の借用があることによって a.y に制限がかかってしまうのです。


この例で

rust

1println!("{}{}", b,c);

の部分を削除すると &mut a.y; は可能になります。 変数の寿命は通常はスコープの終わりなのですが、使われないことがわかっているときは早めに寿命を終えることがあるからです。 b が使われないことを処理系が看破したらその場で借用を終えてしまうのでそれに依存している a の借用も消えて新たな借用が可能になります。


質問の事例に当てはめると、

  • player = self.get_next_player();self 全体が借用されていることになる
  • self.item.change();self が借用されている影響で self.item が制限される
  • player.play();player を実際に使うことによって早目に寿命を終えることを許さない

ということになります。

私自身が正確な仕様を理解しているわけではありませんが大まかに関連するルールとしてはたぶん外してないと思います。

投稿2022/12/13 10:48

SaitoAtsushi

総合スコア5437

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rim_yamamoto

2022/12/13 15:22 編集

丁寧な回答ありがとうございます。 > ここで a.y 自体は借用されていませんけども a の借用があることによって a.y に制限がかかってしまうのです。 この点を理解していませんでした。 if trueを消すとエラーが出なくなる点については、改めて考えて整理がついたので、そちらも回答に記載しておきます。何かツッコミどころがあればコメントお願いいたします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問