我正在尝试在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
--> srcmain.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
--> srcmain.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
--> srcmain.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
好的,现在我们准备一步一步地看看编译器在想什么:
{ // start 'v let vincent = Officer::new(8); // : Officer<'?arg_vincent>
Rust还不知道lifetime参数,因此它只能在这里推导出一个不完整的类型。希望我们稍后能填写详细信息!当编译器想要显示缺少的类型信息时,它会打印一个下划线(例如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
=>'v:'a`寿命长 - 最后,
Policeman<'a>
中的'a
指的是(尚未确定的)寿命参数'?arg_vincent
(因为这是我们传递的参数)。但是CCD_ 36还不是固定的并且是完全无界的。因此,这并没有对'a
施加限制(与前两点不同)。相反,我们对'a
的选择决定了稍后的'?arg_vincent
:'?arg_vincent := 'a
简而言之:
'j: 'a and
'v: 'a
那么,什么样的一生最多能像约翰一样长寿,最多能像文森特一样长寿呢?'v
是不够的,因为它比john
更长寿。CCD_ 43良好;它满足上述条件。
那么一切都好吗?不我们现在选择了终身'a = 'j
。因此我们也知道'?arg_vincent = 'j
!因此vincent
的完整类型是Officer<'j>
。这反过来告诉编译器vincent
借用了一些生命周期为j
的东西。但vincent
的寿命比'j
长,所以它比它的借用寿命长!太糟糕了。这就是编译器抱怨的原因。
这整件事真的相当复杂,我想在阅读了我的解释后,大多数人的感觉和我阅读大多数数学证明后的感觉完全一样:每一步都有意义,但结果并不直观也许这稍微改善了情况:
由于set_next()
函数要求所有寿命为'a
,因此我们在程序中对所有寿命施加了很多限制。这很快导致了限制方面的矛盾,就像这里发生的那样。
我的小例子的快速修复
是从self
参数中删除'a
:
fn set_next(&mut self, next: &'a Policeman<'a>)
通过这样做,我们消除了不必要的限制。不幸的是,这还不足以编译整个示例。
更通用的解决方案
我对你提到的设计模式不是很熟悉,但从外观上看,在编译时跟踪所涉及的生命周期几乎是不可能的。因此,我会使用Rc
或Arc
来代替引用。有了这些智能指针,您就不需要对生命周期进行注释,一切都"正常工作"。唯一的缺点是运行时开销很小。
但要告诉你最好的解决方案是不可能的:这实际上取决于手头的问题。
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
这是有效的(操场),因为结构Officer
相对于'a
是协变的。这意味着,如果你有一个Officer<'a>
,你可以像对待Officer<'b>
一样对待它,就像对待'a: 'b
一样;也就是说,当'a
超过'b
时,Officer<'a>
是Officer<'b>
的亚型。这些知识使编译器可以按照您最初可能预期的方式缩短每个引用的生存期。(还有一个关于方差的非常好的问答,你可能会喜欢,尽管它不太适用于你的情况。)
另一方面,就其参数而言,性状在变体中总是,因此Policeman<'a>
是而不是Policeman<'b>
的亚型。这剥夺了编译器调整寿命的能力:引用&'_ john
可能有更短的寿命,但Policeman<'_>
特性不能。这就是为什么即使是卢卡斯的"快速修复"也不会对你的整个例子起作用。
至少还有一种方法可以通过添加一个生存期参数来使原始示例工作,这样set_next
就不会统一&'?first Policeman<'?second>
中的两个生存期,但从这一更改中,你只得到了一层额外的间接层——也就是说,它会使示例工作,但如果你添加了向martin
报告的michael
,你就会回到原来的位置。