如何使用特征对象链实现责任链模式?

时间:2017-10-14 10:13:48

标签: design-patterns rust chain-of-responsibility

我试图在Rust中实现Chain of Responsibility设计模式:

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是指vincent(范围内为vincent
  5. martin指的是范围内的john (john
  6. martin超出范围(john仍在范围内)
  7. john超出范围(vincent仍在范围内)
  8. vincent超出范围
  9. 如何更改生命周期或代码以正确实现Rust中的责任链模式?

2 个答案:

答案 0 :(得分:7)

详细说明

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

为了使它更容易一些,我们使用这个简化的例子:

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还不知道life参数,因此它只能在这里推断出一个不完整的类型。希望我们以后可以填写详细信息!当编译器想要显示缺少的类型信息时,它会打印下划线(例如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 ...nextvincent,所以(就像上面一样),'a的寿命不会超过vincent'v 超过 'a =&gt; &#39; v:&#39; a`。
  • 最后,'a中的Policeman<'a>引用(尚未确定)生命周期参数'?arg_vincent(因为我们传递的是参数)。但'?arg_vincent尚未修复且完全无界限。所以这并没有对'a施加限制(与前两点不同)。相反,我们对'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>)

通过这样做,我们删除了不必要的限制。不幸的是,这还不足以使您的整个示例编译。

更通用的解决方案

我对你提到的设计模式不是很熟悉,但从它的外观来看,在编译时跟踪所涉及的生命周期几乎是不可能的。因此,我使用RcArc代替参考。通过这些智能指针,您不需要注释生命周期和所有内容,而只需要工作&#34;。唯一的缺点:运行成本很小。

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

答案 1 :(得分:7)

Lukas's excellent answer解释了为什么这不起作用,您应该考虑使用智能指针 - Box用于单一所有权,或Rc / Arc用于共享所有权。

也就是说,你可以通过摆脱Policeman特征并使set_next固有Officer来做类似的事情(尽管不是很有用):

pub struct Officer<'a> {
    deduction: u8,
    next: Option<&'a Officer<'a>>,
}

impl<'a> Officer<'a> {
    pub fn new(deduction: u8) -> Officer<'a> {
        Officer {deduction, next: None}
    }
    fn set_next(&mut self, next: &'a Officer<'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

这有效(playground),因为结构Officer相对于'acovariant。这意味着如果您有Officer<'a>,只要Officer<'b>,就可以将其视为'a: 'b;也就是说,当'a超过'b时,Officer<'a>Officer<'b>子类型。这种知识使编译器能够以您最初预期的方式缩短每个引用的生命周期。 (还有另一个really good Q&A关于您可能喜欢的差异,尽管它并不完全适用于您的情况。)

Traits, on the other hand, are always invariant with respect to their parameters,因此Policeman<'a> Policeman<'b>的子类型。这剥夺了编译器调整生命周期的能力:引用&'_ john可能具有更短的生命周期,但Policeman<'_>特征不能。这就是为什么即使Lukas的“快速解决方案”也不适用于你的整个例子。

至少还有一种方法可以通过添加一个生命周期参数来使原始示例工作,以便set_next不会统一&'?first Policeman<'?second>中的两个生命周期,但是从这个更改中你只能获得一个额外的生命周期间接层 - 也就是说,它会使示例工作,但如果您添加了向michael报告的martin,您就会回到原来的位置。