長めの回答になってしまいますがご了承ください。
&'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`
ここで重要なのが、
X<'a>
がライフタイムパラメータ 'a
を持っているということ
'a
が exec()
のレシーバの中で指定されていること
X
が Drop
を実装していること
の 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
を呼び出す際に作られる可変参照 xref
は x
が生きている間のみ有効でなければならないので、そのライフタイム(これを仮に '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<'_>`
慣れていても面食らうエラーメッセージだと思いますが、要するに先ほどの例と同じで、
v
のライフタイムは 'b
よりも短くなければいけない(一時的なものなので)
v
のライフタイムは 'a
と同じか、それよりも長くなければいけない(Drop
時に無効だとまずいので)
という二つの要件が矛盾していることがこのエラーの原因になっています。ここで 2 つ目の制約は、ui
が ControllerSample
のメンバとしてあることによって要求されてたものです。そのため、次のように ui
を ControllerSample
のメンバとして持たず、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()
してしまうのが無難な設計だと思われます。コピーコストが気になる場合は Rc
や Arc
などのスマートポインタを用いることを検討すると良いかもしれません。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/09/22 15:13