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

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

新規登録して質問してみよう
ただいま回答率
85.35%
参照

参照は、プログラミングにおいて変数や関数といったメモリ空間上での所在を指示するデータのことを指します。その中にはデータ自体は含まれず、他の場所にある情報を間接的に指示するプログラムです。

MVC

MVC(Model View Controller)は、オブジェクト指向プログラミングにおけるモデル・ビュー・コントローラーの総称であり、ソフトフェア開発で使われている構築パターンとしても呼ばれます。

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

Rust

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

Q&A

解決済

1回答

2575閲覧

Rust: mpscで参照のVecを渡すとライフタイムのエラーが出る

TAKOYAKING8

総合スコア3

参照

参照は、プログラミングにおいて変数や関数といったメモリ空間上での所在を指示するデータのことを指します。その中にはデータ自体は含まれず、他の場所にある情報を間接的に指示するプログラムです。

MVC

MVC(Model View Controller)は、オブジェクト指向プログラミングにおけるモデル・ビュー・コントローラーの総称であり、ソフトフェア開発で使われている構築パターンとしても呼ばれます。

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

Rust

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

1グッド

1クリップ

投稿2020/09/20 16:23

前提・実現したいこと

MVC参考記事
上記の参考記事のようにデータとview構造を分けたいと思い、mpscを利用して、view側に通知するコードを書きました。
しかし、view側へ参照のvecを渡そうとするとライフタイムのエラーが出てしまい、ライフタイムの問題を解決できませんでした。
mpsc, HashMapの値がVecという条件でライフタイムを解決するにはどのようにすれば良いのでしょうか?

以下がやりたいことのデータ構造の大まかな流れです。

  • ui側からeventを受け取る。(ここではソースコードは関係ないので省略しています。)
  • controllerはそのeventデータを元にui側にHashMapから取得したvecを渡す。 (今回の問題点です。)
  • ui側はその情報を描画する (ここではソースコードは関係ないので省略しています。)

以上お手数ですが、よろしくお願いいたします。

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

HashMapで値を取得できるようにexec関数のselfのライフタイムを&'a mut selfにすると以下のようなエラーが出ました。

error[E0597]: `controller_sample` does not live long enough --> src/main.rs:7:5 | 7 | controller_sample.exec(); // &'a mut selfにするとエラーが出てしまう。 | ^^^^^^^^^^^^^^^^^ borrowed value does not live long enough 8 | } | - | | | `controller_sample` dropped here while still borrowed | borrow might be used here, when `controller_sample` is dropped and runs the destructor for type `ControllerSample<'_>`

該当のソースコード

rust

1use std::collections::HashMap; 2use std::sync::mpsc; 3 4 5fn main() { 6 let mut controller_sample = ControllerSample::new(); 7 controller_sample.exec(); // &'a mut selfにするとエラーが出てしまう。 8} 9 10struct ControllerSample<'a> { 11 map: HashMap<String, Vec<i32>>, 12 ui: UiSample<'a>, 13} 14 15impl<'a> ControllerSample<'a> { 16 fn new() -> ControllerSample<'a> { 17 let mut map: HashMap<String, Vec<i32>> = HashMap::new(); 18 map.insert(String::from("sample"), vec![1, 2, 3]); 19 ControllerSample { 20 map: map, 21 ui: UiSample::new(), 22 } 23 } 24 25 // fn test(&'a mut self) { 26 fn exec(&'a mut self) { 27 // key: keyはui側からイベントで送られる想定ですが、ここでは暫定的に以下のものにしています。 28 let key = &String::from("sample"); 29 30 // &'a mut selfで'aを明示しないとコンパイルエラー "cannot infer" 31 let v = self.map.get(key).unwrap(); 32 33 // ui側に送信する。 (※受信側は省略しています。) 34 self.ui 35 .ui_tx 36 .send(UiMessageSample::UpdateContents(v)) 37 .unwrap(); 38 } 39} 40 41struct UiSample<'a> { 42 ui_tx: mpsc::Sender<UiMessageSample<'a>>, 43} 44 45impl<'a> UiSample<'a> { 46 fn new() -> UiSample<'a> { 47 let (ui_tx, _) = mpsc::channel::<UiMessageSample>(); 48 UiSample { 49 ui_tx: ui_tx, 50 } 51 } 52} 53 54enum UiMessageSample<'a> { 55 // 毎度値をコピーして渡すのは嫌だったので参照にしたい。 56 UpdateContents(&'a Vec<i32>), 57}

試したこと

  • controller_sample.exec()のエラーを消すために&'a mut selfを& mut selfにするとエラーは消えるのですが、今度はexec関数の中のHashMapから値を取得する箇所でライフタイムを推測できないとエラーが出てしまいます。
  • 逆にHashMapのエラーを消すためにexec関数で&'a mut selfと指定すると、HashMapのエラーが消えるのですが、controller_sample.exec()でエラーが出てしまいます。

補足情報

上記のコードは以下のplay groundで試せます。
Play Ground

denjiry👍を押しています

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

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

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

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

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

guest

回答1

0

ベストアンサー

長めの回答になってしまいますがご了承ください。

&'a mut self としたときのコンパイルエラーについて

まずはじめに、提示されているコンパイルエラーが生じる原因を考えてみたいと思います。本筋ではないため、興味がないのであれば読み飛ばしていただいてもかまいません。また、回答者は Rust コンパイラや標準ライブラリの実装に精通しているわけではなく、実際の原因とは異なっているかもしれませんがご了承ください。

分析を簡単にするため、次のように単純化したケースを考えることにします。

rust

1use std::marker::PhantomData; 2 3struct X<'a>(PhantomData<&'a ()>); 4 5impl<'a> X<'a> { 6 fn exec(&'a mut self) {} 7} 8 9impl Drop for X<'_> { 10 fn drop(&mut self) {} 11} 12 13fn main() { 14 let mut x = X(PhantomData); 15 16 let xref = &mut x; 17 xref.exec(); 18}

txt

1error[E0597]: `x` does not live long enough 2 --> src/lib.rs:16:16 3 | 416 | let xref = &mut x; 5 | ^^^^^^ borrowed value does not live long enough 617 | xref.exec(); 718 | } 8 | - 9 | | 10 | `x` dropped here while still borrowed 11 | borrow might be used here, when `x` is dropped and runs the `Drop` code for type `X`

ここで重要なのが、

  1. X<'a> がライフタイムパラメータ 'a を持っているということ
  2. 'aexec() のレシーバの中で指定されていること
  3. XDrop を実装していること

の 3 点です。これらの点を踏まえて、main 関数の中を観察していきます。

rust

1fn main() { 2 let mut x = X(PhantomData); // +- start 'x 3 // | 4 let xref = &mut x; // | +- start 'xref 5 xref.exec(); // | | 6 // drop(xref); // | +- end 'xref 7 // | 8 // drop(x); // +- end 'x 9}

exec を呼び出す際に作られる可変参照 xrefx が生きている間のみ有効でなければならないので、そのライフタイム(これを仮に 'xref と呼ぶことにします)は x のライフタイム( 'x と呼ぶことにします)よりも小さい必要があります。

一方、X<'a>Drop を実装しているということは、ライフタイム 'a は少なくとも X のインスタンスが破棄されるまでは有効でなければいけないことを意味します(Drop 内でダングリングポインタを触ってはいけないため)。そのため、'a'x よりも大きい必要があります。

以上の話をまとめると、'xref はほかの 2 つのライフタイム(特に 'a)よりも小さくなければいけないことがわかります。しかし、exec(&'a mut self) となっているため、'xref'a より大きくないといけないことになってしまいます。この2つの条件で矛盾が生じていることがライフタイム周りのエラーが生じている原因だと考えられます。実際にコンパイルエラーメッセージを読むと、同じようなことが書かれていることが確認できます。

修正案

exec(&'a mut self) だと矛盾が生じてしまうことが分かったので、'a に固定せず任意のライフタイムパラメータを取るように exec のシグネチャを修正した場合から考えていきます。lifetime elision をせず &mut self のライフタイムパラメータを明示的に記述すると次のようになります。

rust

1impl<'a> ControllerSample<'a> { 2 fn exec<'b>(&'b mut self) 3 where 4 'a: 'b, 5 { 6 let key = &String::from("sample"); 7 8 let v = self.map.get(key).unwrap(); 9 10 self.ui 11 .ui_tx 12 .send(UiMessageSample::UpdateContents(v)) 13 .unwrap(); 14 } 15}

txt

1error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements 2 --> src/lib.rs:31:26 3 | 431 | let v = self.map.get(key).unwrap(); 5 | ^^^ 6 | 7note: first, the lifetime cannot outlive the lifetime `'b` as defined on the method body at 25:13... 8 --> src/lib.rs:25:13 9 | 1025 | fn exec<'b>(&'b mut self) 11 | ^^ 12note: ...so that reference does not outlive borrowed content 13 --> src/lib.rs:31:17 14 | 1531 | let v = self.map.get(key).unwrap(); 16 | ^^^^^^^^ 17note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 15:6... 18 --> src/lib.rs:15:6 19 | 2015 | impl<'a> ControllerSample<'a> { 21 | ^^ 22note: ...so that the expression is assignable 23 --> src/lib.rs:35:19 24 | 2535 | .send(UiMessageSample::UpdateContents(v)) 26 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 27 = note: expected `UiMessageSample<'a>` 28 found `UiMessageSample<'_>`

慣れていても面食らうエラーメッセージだと思いますが、要するに先ほどの例と同じで、

  1. v のライフタイムは 'b よりも短くなければいけない(一時的なものなので)
  2. v のライフタイムは 'a と同じか、それよりも長くなければいけない(Drop 時に無効だとまずいので)

という二つの要件が矛盾していることがこのエラーの原因になっています。ここで 2 つ目の制約は、uiControllerSample のメンバとしてあることによって要求されてたものです。そのため、次のように uiControllerSample のメンバとして持たず、exec の呼び出し時に引数として参照を渡してあげる事でライフタイム周りの問題を解消することができます。

rust

1use std::collections::HashMap; 2use std::sync::mpsc; 3 4fn main() { 5 let mut controller_sample = ControllerSample::new(); 6 let ui = UiSample::new(); 7 controller_sample.exec(&ui); 8} 9 10struct ControllerSample { 11 map: HashMap<String, Vec<i32>>, 12} 13 14impl ControllerSample { 15 fn new() -> ControllerSample { 16 let mut map: HashMap<String, Vec<i32>> = HashMap::new(); 17 map.insert(String::from("sample"), vec![1, 2, 3]); 18 ControllerSample { 19 map: map, 20 } 21 } 22 23 fn exec<'a>(&'a mut self, ui: &UiSample<'a>) { 24 let key = &String::from("sample"); 25 let v = self.map.get(key).unwrap(); 26 ui.ui_tx 27 .send(UiMessageSample::UpdateContents(v)) 28 .unwrap(); 29 } 30} 31 32struct UiSample<'a> { 33 ui_tx: mpsc::Sender<UiMessageSample<'a>>, 34} 35 36impl<'a> UiSample<'a> { 37 fn new() -> UiSample<'a> { 38 let (ui_tx, _) = mpsc::channel::<UiMessageSample>(); 39 UiSample { 40 ui_tx: ui_tx, 41 } 42 } 43} 44 45enum UiMessageSample<'a> { 46 UpdateContents(&'a Vec<i32>), 47}

余談

ライフタイム周りの問題点は解消されましたが、exec を二回以上呼ぼうとしたときに次のようなエラーメッセージが出てコンパイルが通りません。

error[E0499]: cannot borrow `controller_sample` as mutable more than once at a time --> src/lib.rs:12:5 | 11 | controller_sample.exec(&ui_sample); | ----------------- first mutable borrow occurs here 12 | controller_sample.exec(&ui_sample); | ^^^^^^^^^^^^^^^^^ ---------- first borrow later used here | | | second mutable borrow occurs here

これは、一回目の exec 呼び出し時に作られた &'a mut self の生存範囲が ui_sample が破棄されるまで続くと borrow checker が判断するためです。実際に UiMessageSample が持つのは共有参照ですが、exec のシグネチャからはそれを読み取ることが出来ないためこのような結果になります。こちらのページも併せて参照してみてください。

経験的に、参照を関数・メソッドの外で使えるように保持しようとすると今回のような問題が生じやすいです。コピーコストを避けるのも大事ですが、今回のように各データが複雑に依存する状況下では無理して参照を保持しようとせず、おとなしく clone() してしまうのが無難な設計だと思われます。コピーコストが気になる場合は RcArc などのスマートポインタを用いることを検討すると良いかもしれません。

投稿2020/09/22 13:40

ubnt-intrepid

総合スコア58

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

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

TAKOYAKING8

2020/09/22 15:13

丁寧に解説していただき、ありがとうございます。 わかりやすいサンプルを付けていただき、ライフタイムの矛盾について理解することできました。 ご提案いただいた、cloneまたはrcを使った代替案で進めていきたいと思います。 とても困っていたので大変助かりました、ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問