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

Ark*_*sov 6 design-patterns chain-of-responsibility rust

我正在尝试在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
Run Code Online (Sandbox Code Playgroud)

这会产生错误消息:

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
Run Code Online (Sandbox Code Playgroud)

为什么john活得不够长?

  1. 创建 vincent
  2. 创建 john
  3. 创建 martin
  4. johnvincent(vincent范围内)
  5. martin是指john (john在范围)
  6. martin超出范围(john仍在范围内)
  7. john超出范围(vincent仍在范围内)
  8. vincent 超出范围

如何更改生命周期或代码以正确实现Rust中的责任链模式?

Luk*_*odt 7

详细解释

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

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

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

john.set_next(&vincent);
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

首先,让我们以一种更明确,更明智的形式转换代码:

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

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

        john.set_next(&vincent);    
    } // end 'j
} // end 'v
Run Code Online (Sandbox Code Playgroud)

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

{ // start 'v
    let vincent = Officer::new(8); // : Officer<'?arg_vincent>
Run Code Online (Sandbox Code Playgroud)

Rust还不知道生命周期参数,因此它只能在这里推断出一个不完整的类型.希望我们以后可以填写详细信息!当编译器想要显示缺少的类型信息时,它会打印下划线(例如Vec<_>).在这个例子中,我把缺失的信息写成了'?arg_vincent.那样我们以后可以参考它.

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

与上述相同.

        john.set_next(&vincent);
Run Code Online (Sandbox Code Playgroud)

现在它变得有趣了!编译器具有此功能签名:

fn set_next(&'a mut self, next: &'a Policeman<'a>)
Run Code Online (Sandbox Code Playgroud)

现在,编译器的工作是找到'a满足一系列条件的拟合生命周期:

  • 我们已经&'a mut self并且johnself这里.所以'a不能活得比john.换句话说:'j outlives 'a,表示'j: 'a.
  • 我们拥有next: &'a ...nextvincent,所以(就像上面一样),'a不能活得更久vincent.'v outlives 'a =>'v:'a`.
  • 最后,'ain Policeman<'a>指的是(尚未确定)生命周期参数'?arg_vincent(因为这是我们作为参数传递的).但'?arg_vincent尚未修复且完全无界限.所以这并没有施加限制'a(与前两点不同).相反,我们的选择'a决定了'?arg_vincent以后:'?arg_vincent := 'a.

简而言之:

'j: 'a    and
'v: 'a
Run Code Online (Sandbox Code Playgroud)

那么,什么是它最多只要生活约翰一生大多数只要文森特?'v是不够的,因为它超过了john.'j很好; 它满足了上述条件.

一切都好吗?没有!我们'a = 'j现在选择了一生.因此我们也知道'?arg_vincent = 'j!所以完整的类型vincentOfficer<'j>.这反过来告诉编译器vincent借用了生命周期的东西j.但是vincent寿命比'j它长,所以它比它的借款更长!那很糟.这就是编译器抱怨的原因.

这一切都非常复杂,我想在看完我的解释之后,大多数人在阅读大多数数学证据之后感觉完全像我感觉:每一步都有道理,但结果并不直观.也许这会略微改善这种情况:

由于该set_next()函数需要所有生命周期,因此 'a我们对程序中的所有生命周期施加了很多限制.这很快就会导致限制的矛盾,因为它发生在这里.

快速修复我的小例子

...是'aself参数中删除:

fn set_next(&mut self, next: &'a Policeman<'a>)
Run Code Online (Sandbox Code Playgroud)

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

更通用的解决方案

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

但是不可能告诉你最好的解决方案:它真的取决于手头的问题.


tre*_*tcl 7

Lukas的优秀答案解释了为什么这不起作用,你应该考虑使用智能指针 - 无论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
Run Code Online (Sandbox Code Playgroud)

此作品(操场),因为该结构Officer相对于'a.这意味着,如果你有一个Officer<'a>,你可以把它当作一个Officer<'b>只要'a: 'b; 即,当'a会超越'b,Officer<'a>是一个亚型Officer<'b>.这种知识使编译器能够以您最初预期的方式缩短每个引用的生命周期.(还有一个非常好的关于你可能喜欢的差异的问答,虽然它并不完全适用于你的情况.)

性状,而另一方面,总是相对于它们的参数变异,所以Policeman<'a>不是一个亚型Policeman<'b>.这剥夺了编译器调整生命周期的能力:参考&'_ john可能具有更短的生命周期,但Policeman<'_>特征不能.这就是为什么即使Lukas的"快速解决方案"也不适用于你的整个例子.

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