我正在尝试在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

这将产生错误消息:

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中正确实现责任链模式?

    最佳答案

    详细说明

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

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

    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
    

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



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



    与上述相同。



    现在变得很有趣了!编译器具有以下功能签名:
    fn set_next(&'a mut self, next: &'a Policeman<'a>)
    

    现在,编译器的工作是找到满足一堆条件的合适的生命周期'a:
  • 这里有&'a mut self,而johnself。因此,'a的生命周期不能超过john的生命周期。换句话说:'j超过'a(表示为'j: 'a)。
  • 我们有next: &'a ...,而nextvincent,因此(就像上面一样),'a的生存期不能超过vincent'v超过'a =>'v:'a`。
  • 最后,'a中的Policeman<'a>引用(尚未确定)生命周期参数'?arg_vincent(因为这就是我们作为参数传递的内容)。但是'?arg_vincent尚未修复,并且完全不受限制。因此,这并没有对'a施加限制(与前两点不同)。相反,我们对'a的选择将在以后确定'?arg_vincent:'?arg_vincent := 'a

  • 简而言之:
    'j: 'a    and
    'v: 'a
    

    那么,一生中最多能和john 一样长,与Vincent一样长的生命是什么? '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代替引用。使用这些智能指针,您无需注释生命周期,一切都“可行”。唯一的缺点是:运行时间成本很小。

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

    关于design-patterns - 如何使用特征对象链实现责任链模式?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/46743353/

    10-11 01:01