RustでChainofResponsibilityデザインパターンを実装しようとしています。
pub trait Policeman<'a> {
fn set_next(&'a mut self, next: &'a Policeman<'a>);
}
pub struct Officer<'a> {
deduction: u8,
next: Option<&'a Policeman<'a>>,
}
impl<'a> Officer<'a> {
pub fn new(deduction: u8) -> Officer<'a> {
Officer {deduction, next: None}
}
}
impl<'a> Policeman<'a> for Officer<'a> {
fn set_next(&'a mut self, next: &'a Policeman<'a>) {
self.next = Some(next);
}
}
fn main() {
let vincent = Officer::new(8); // -+ vincent enters the scope
let mut john = Officer::new(5); // -+ john enters the scope
let mut martin = Officer::new(3); // -+ martin enters the scope
// |
john.set_next(&vincent); // |
martin.set_next(&john); // |
} // martin, john, vincent out of scope
これにより、エラーメッセージが生成されます。
error[E0597]: `john` does not live long enough
--> src\main.rs:29:1
|
27 | john.set_next(&vincent);
| ---- borrow occurs here
28 | martin.set_next(&john);
29 | }
| ^ `john` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
error[E0597]: `martin` does not live long enough
--> src\main.rs:29:1
|
28 | martin.set_next(&john);
| ------ borrow occurs here
29 | }
| ^ `martin` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
error[E0597]: `john` does not live long enough
--> src\main.rs:29:1
|
28 | martin.set_next(&john);
| ---- borrow occurs here
29 | }
| ^ `john` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
なぜjohn
十分に長生きしないのですか?
vincent
john
martin
john
参照vincent
(vincent
範囲内)martin
意味するjohn (john
)の範囲でmartin
範囲外(john
まだ範囲内)john
範囲外(vincent
まだ範囲内)vincent
範囲外RustでChainof Responsibilityパターンを正しく実装するには、ライフタイムまたはコードを変更するにはどうすればよいですか?
あなたの問題は非常に興味深いものであり、なぜそれが機能しないのかを直接理解するのは確かに難しいです。コンパイラがどのように統合を行うかを理解していれば、非常に役立ちます。型を見つけるためにコンパイラーが実行するすべてのステップをウォークスルーします。
少し簡単にするために、次の簡略化された例を使用します。
let vincent = Officer::new(8);
let mut john = Officer::new(5);
john.set_next(&vincent);
これにより、同じエラーメッセージが表示されます。
error[E0597]: `john` does not live long enough
--> src/main.rs:26:1
|
25 | john.set_next(&vincent);
| ---- borrow occurs here
26 | }
| ^ `john` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
まず、生涯にわたってより明確な形式にコードを変換しましょう。
{ // start 'v
let vincent = Officer::new(8);
{ // start 'j
let mut john = Officer::new(5);
john.set_next(&vincent);
} // end 'j
} // end 'v
これで、コンパイラが何を考えているかを段階的に確認する準備が整いました。
{ // start 'v let vincent = Officer::new(8); // : Officer<'?arg_vincent>
Rustはまだlifetimeパラメータを知らないので、ここでは不完全な型しか推測できません。後で詳細を記入できるといいのですが!コンパイラーが欠落している型情報を表示したい場合、アンダースコア(eg Vec<_>
)を出力します。この例では、不足している情報をとして記述しています'?arg_vincent
。そうすれば、後で参照できます。
{ // start 'j let mut john = Officer::new(5); // : Officer<'?arg_john>
上記と同じです。
john.set_next(&vincent);
今では面白くなります!コンパイラには、次の関数シグネチャがあります。
fn set_next(&'a mut self, next: &'a Policeman<'a>)
さて、コンパイラの仕事は'a
、一連の条件を満たす適切な寿命を見つけることです。
&'a mut self
とjohn
されself
、ここで。したがって、'a
より長く生きることはできませんjohn
。言い換えれば、より'j
長生き 'a
します'j: 'a
。next: &'a ...
てnext
いるしvincent
、そうなので(上記のように)、'a
より長く生きることはできませんvincent
。'v
寿命 'a
=> 'v:' a`。'a
inPoliceman<'a>
は(まだ決定されていない)存続期間パラメーターを参照します'?arg_vincent
(これが引数として渡されるため)。しかし'?arg_vincent
、まだ修正されておらず、完全に無制限です。したがって、これは'a
(前の2つのポイントとは異なり)制限を課しません。代わりに、の選択は後で'a
決定し'?arg_vincent
ます'?arg_vincent := 'a
。つまり、要するに:
'j: 'a and
'v: 'a
それで、せいぜいジョンと同じくらい長く、そしてヴィンセントと同じくらい長く生きる生涯は何ですか?'v
それは長生きするので、十分ではありませんjohn
。'j
結構です; 上記の条件を満たす。
だからすべてが大丈夫ですか?番号!私たちは'a = 'j
今、生涯を選びました。したがって、私たちはそれも知ってい'?arg_vincent = 'j
ます!したがって、の完全なタイプはvincent
ですOfficer<'j>
。これは次にvincent
、ライフタイムで何かを借用したコンパイラに通知しj
ます。しかし、vincent
より長生きするので'j
、借りるよりも長生きします!それは良くないね。コンパイラが文句を言うのはそのためです。
この全体は本当にかなり複雑で、私の説明を読んだ後、ほとんどの人はほとんどの数学の証明を読んだ後とまったく同じように感じると思います。各ステップは理にかなっていますが、結果は直感的ではありません。多分これは状況をわずかに改善します:
このset_next()
関数ではすべてのライフタイムがである 必要があるため、'a
プログラムのすべてのライフタイムに多くの制限を課します。これは、ここで起こったように、すぐに制限の矛盾につながります。
...はパラメータ'a
からを削除することですself
:
fn set_next(&mut self, next: &'a Policeman<'a>)
そうすることで、不要な制限を取り除きます。残念ながら、これだけではサンプル全体をコンパイルするのに十分ではありません。
あなたが言及しているデザインパターンについてはあまり詳しくありませんが、その外観からすると、コンパイル時に関連するライフタイムを追跡することはほぼ不可能です。したがって、参照の代わりに、Rc
またはArc
を使用します。これらのスマートポインターを使用すると、ライフタイムに注釈を付ける必要がなく、すべてが「正常に機能」します。唯一の欠点:実行時のコストがわずかです。
しかし、最善の解決策を伝えることは不可能です。それは実際には目前の問題に依存します。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加