如何使用特征对象链实现责任链模式? [英] How do I implement the Chain of Responsibility pattern using a chain of trait objects?

查看:86
本文介绍了如何使用特征对象链实现责任链模式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在Rust中实施责任链设计模式:

  pub trait Policeman<’a> {
fn set_next(&’mut a self,next:&’a Policeman<’a>);
}

酒吧结构官<’a> {
扣减项:u8,
下一步:Option&’a警察'a>>
}

暗示*军官{
pub fn new(deduction:u8)->军官{
官员{扣,下一个:无}
}
}

impl<’a>警察<’a>对于官员 {
fn set_next(&’a mut self,next:&’a Policeman<’a>){
self.next = Some(next);
}
}

fn main(){
let vincent = Officer :: new(8); //-++ Vincent进入范围
let mut john = Officer :: new(5); //-+ john进入范围
let mut martin = Officer :: new(3); //-+马丁进入范围
// |
john.set_next(& vincent); // |
martin.set_next(& john); // |
} //马丁,约翰,文森特超出范围

这会产生错误消息:

 错误[E0597]:`john`寿命不足
- > src\main.rs:29:1
|
27 | john.set_next(& vincent);
| ----这里借钱
28 | martin.set_next(& john);
29 | }
| ^`john`仍在这里借钱
|
=注意:作用域中的值以与创建它们相反的顺序被删除

错误[E0597]:`martin`寿命不足
-> src\main.rs:29:1
|
28 | martin.set_next(& john);
| ------这里借钱
29 | }
| ^`马丁`下降了这里,但仍然借了
|
=注意:作用域中的值以与创建它们相反的顺序被删除

错误[E0597]:`john`寿命不足
-> src\main.rs:29:1
|
28 | martin.set_next(& john);
| ----这里借钱
29 | }
| ^`john`仍在这里借钱
|
=注意:作用域中的值以创建时的相反顺序删除

为什么 john 寿命不够长吗?


  1. 已创建文森特

  2. 已创建 john

  3. 已创建 martin

  4. john 是指 vincent (范围是 vincent

  5. martin 是指 john(作用域为john

  6. martin 超出范围( john 仍在范围内)

  7. john 超出范围( vincent 仍在范围内)

  8. vincent 超出范围

我该如何更改生存期或代码正确地在Rust中实施责任链模式?

解决方案

详细说明



您的问题非常有趣,并且很难直接理解为什么它不起作用。如果您了解编译器是如何进行统一的,那么它会很有帮助。我们将遍历编译器执行的所有步骤以找出类型。



为了使操作更简单,我们使用以下简化示例:

 让vincent = Officer :: new(8); 
let mut john = Officer :: new(5);

john.set_next(& vincent);

这将导致相同的错误消息:



< pre $ = lang-none prettyprint-override> error [E0597]:`john`寿命不足
-> src / main.rs:26:1
|
25 | john.set_next(& vincent);
| ----这里借钱
26 | }
| ^`john`仍在这里借钱
|
=注意:作用域中的值按创建时的相反顺序删除

First ,让我们以更明确,更终身的方式转换代码:

  {//开始'v 
let vincent = Officer :: new(8);

{//开始’j
let mut john = Officer :: new(5);

john.set_next(& vincent);
} //结束'j
} //结束'v






好,现在我们准备逐步了解编译器的想法:


  {//开始'v 
let vincent = Officer :: new(8); //:官员<’?arg_vincent>


Rust还不知道寿命参数,因此可以仅在此处推导不完整的类型。希望我们以后可以填写详细信息!当编译器要显示缺少的类型信息时,它会打印一个下划线(例如 Vec< _> )。在此示例中,我将丢失的信息写为'?arg_vincent 。这样,我们以后可以参考它。


  {//开始'j 
let mut john =官员:: new(5); //:官员<’?arg_john>


与上述相同。


  john.set_next(& vincent); 


现在变得很有趣了!编译器具有以下功能签名:

  fn set_next(&'mut a 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`。

  • 最后, Policeman<'a> 中的'a 是指(待定)的生存期参数'?arg_vincent (因为这就是我们作为参数传递的内容)。但是’?arg_vincent 尚未修复,并且完全不受限制。因此,这不会对’a 施加限制(与前两点不同)。相反,我们对'a 的选择决定了'?arg_vincent 以后:'?arg_vincent:= 'a



简而言之:

 'j:'a和
'v:'a

那么,像约翰一样与文森特一样多的生命寿命是多少? ’v 是不够的,因为它比 john 寿命长。 ’j 可以;它满足上述条件。



那么一切都很好吗?没有!我们现在选择生命周期’a =’j 。因此我们也知道'?arg_vincent =‘j !因此,文森特的完整类型为 Officer<’j> 。这反过来告诉编译器, vincent 借用了寿命为 j 的内容。但是 vincent 的寿命比’j 的寿命长,所以它比借来的还长!那很糟。这就是编译器抱怨的原因。



这整个过程确实相当复杂,我想在阅读我的解释后,大多数人在阅读了大多数数学证明后的感觉与我完全一样:每一步都是有意义的,但是结果并不直观。 也许这可以稍微改善这种情况:



由于 set_next()函数需要所有 be 'a 的生命周期,我们对程序中的所有生命周期都施加了很多限制。



我的小例子的快速解决方法



...是从 self 参数中删除'a code>:

  fn set_next(& mut self,next:&'a Policeman<'a>)

这样做可以消除不必要的限制。不幸的是,这还不足以编译整个示例。



更通用的解决方案



我对您提到的设计模式不是很熟悉,但是从外观上看,在编译时跟踪所涉及的生命周期几乎是不可能的。因此,我将使用 Rc Arc 代替引用。有了这些智能指针,您就无需注释生命周期,一切都可行。唯一的缺点是:运行时间成本很小。



但是不可能告诉您最佳的解决方案:这实际上取决于当前的问题。


I'm trying to implement the Chain of Responsibility design pattern in Rust:

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

This produces the error message:

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

Why does john not live long enough?

  1. Created vincent
  2. Created john
  3. Created martin
  4. john refers to vincent (vincent in scope)
  5. martin refers to john (john in scope)
  6. martin out of scope (john still in scope)
  7. john out of scope (vincent still in scope)
  8. vincent out of scope

How do I need to change the lifetimes or the code to correctly implement the Chain of Responsibility pattern in Rust?

解决方案

Detailed explanation

Your problem is quite interesting and it's certainly hard to understand directly why it doesn't work. It helps a lot if you understand how the compiler does unification. We will walk through all steps the compiler does in order to find out types.

In order to make it a bit easier, we use this simplified example:

let vincent = Officer::new(8);
let mut john = Officer::new(5);

john.set_next(&vincent);

This results in the same error message:

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

First, let's transform the code in a form that's more explicit, lifetime wise:

{ // start 'v
    let vincent = Officer::new(8);   

    { // start 'j
        let mut john = Officer::new(5);  

        john.set_next(&vincent);    
    } // end 'j
} // end 'v


Ok, now we're prepared to see what the compiler is thinking, step by step:

{ // start 'v
    let vincent = Officer::new(8); // : Officer<'?arg_vincent>

Rust doesn't know the lifetime parameter yet, thus it can only deduce an incomplete type here. Hopefully we can fill out the details later! When the compiler wants to show missing type information, it prints an underscore (e.g. Vec<_>). In this example I've written the missing information as '?arg_vincent. That way we can refer to it later.

    { // start 'j
        let mut john = Officer::new(5); // : Officer<'?arg_john>

The same as above.

        john.set_next(&vincent);

Now it get's interesting! The compiler has this function signature:

fn set_next(&'a mut self, next: &'a Policeman<'a>)

Now, the compiler's job is to find a fitting lifetime 'a that satisfies a bunch of conditions:

  • We have &'a mut self and john is self here. So 'a can't live longer than john. In other words: 'j outlives 'a, denoted 'j: 'a.
  • We have next: &'a ... and next is vincent, so (just like above), 'a can't live longer than vincent. 'v outlives 'a => 'v: 'a`.
  • Lastly, the 'a in Policeman<'a> refers to the (yet to be determined) lifetime parameter '?arg_vincent (since that's what we pass as argument). But '?arg_vincent is not yet fixed and totally unbounded. So this doesn't impose a restriction on 'a (unlike the previous two points). Instead, our choice for 'a determines'?arg_vincent later: '?arg_vincent := 'a.

So in short:

'j: 'a    and
'v: 'a

So what's a lifetime which lives at most as long as john and as most as long as vincent? 'v isn't sufficient, since it outlives john. 'j is fine; it satisfied the conditions above.

So everything is fine? No! We chose the lifetime 'a = 'j now. Thus we also know that '?arg_vincent = 'j! So the full type of vincent is Officer<'j>. This in turn tells the compiler that vincent borrowed something with the lifetime j. But vincent lives longer than 'j, so it outlives its borrow! That's bad. That's why the compiler complains.

This whole thing is really rather complex, and I guess that after reading my explanation, most people feel exactly like I feel after reading most math proofs: each step made sense, but the result isn't intuitive. Maybe this improves the situation slightly:

Since the set_next() function requires all lifetimes to be 'a, we impose a lot of restrictions on all lifetimes in our program. This quickly leads to contradictions in restrictions, as it happened here.

A quick fix for my small example

... is to remove the 'a from the self parameter:

fn set_next(&mut self, next: &'a Policeman<'a>)

By doing that we remove unnecessary restrictions. Unfortunately, this isn't enough to make your whole example compile.

A more general solution

I'm not very familiar with the design pattern you mentions, but from the looks of it, it's close to impossible to track the involved lifetimes at compile time. Thus I'd use Rc or Arc instead of references. With those smartpointers, you don't need to annotate lifetimes and everything "just works". Only disadvantage: a tiny bit of runtime cost.

But it's impossible to tell you the best solution: it really depends on the problem at hand.

这篇关于如何使用特征对象链实现责任链模式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆