特性オブジェクトのチェーンを使用してChainof Responsibilityパターンを実装するにはどうすればよいですか?

Arkadiy Kuznetsov

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十分に長生きしないのですか?

  1. 作成した vincent
  2. 作成した john
  3. 作成した martin
  4. john参照vincentvincent範囲内)
  5. martin意味するjohn (john)の範囲で
  6. martin範囲外(johnまだ範囲内)
  7. john範囲外(vincentまだ範囲内)
  8. vincent 範囲外

RustでChainof Responsibilityパターンを正しく実装するには、ライフタイムまたはコードを変更するにはどうすればよいですか?

Lukas Kalbertodt

詳細な説明

あなたの問題は非常に興味深いものであり、なぜそれが機能しないのかを直接理解するのは確かに難しいです。コンパイラがどのように統合を行うかを理解していれば、非常に役立ちます。型を見つけるためにコンパイラーが実行するすべてのステップをウォークスルーします。

少し簡単にするために、次の簡略化された例を使用します。

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 selfjohnされself、ここで。したがって、'aより長く生きることはできませんjohn言い換えれば、より'j 長生き 'aします'j: 'a
  • 私たちは持っnext: &'a ...nextいるしvincent、そうなので(上記のように)、'aより長く生きることはできませんvincent'v 寿命 'a=> 'v:' a`。
  • 最後に、'ainPoliceman<'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]

編集
0

コメントを追加

0

関連記事

Related 関連記事

ホットタグ

アーカイブ