我试图在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
的寿命不够长?
vincent
john
martin
john
是指vincent
(范围内为vincent
)martin
指的是范围内的john (john
martin
超出范围(john
仍在范围内)john
超出范围(vincent
仍在范围内)vincent
超出范围如何更改生命周期或代码以正确实现Rust中的责任链模式?
答案 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 self
和john
self
。因此'a
的寿命不会超过john
。换句话说:'j
超过 'a
,表示为'j: 'a
。next: &'a ...
和next
是vincent
,所以(就像上面一样),'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>)
通过这样做,我们删除了不必要的限制。不幸的是,这还不足以使您的整个示例编译。
我对你提到的设计模式不是很熟悉,但从它的外观来看,在编译时跟踪所涉及的生命周期几乎是不可能的。因此,我使用Rc
或Arc
代替参考。通过这些智能指针,您不需要注释生命周期和所有内容,而只需要工作&#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
相对于'a
是covariant。这意味着如果您有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
,您就会回到原来的位置。