如何使用特征对象链来实现责任链模式?
How do I implement the Chain of Responsibility pattern using a chain of trait objects?
我正在尝试在 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
好的,现在我们准备好一步一步地看看编译器在想什么:
{ // start 'v
let vincent = Officer::new(8); // : Officer<'?arg_vincent>
Rust 还不知道生命周期参数,因此它在这里只能推断出一个不完整的类型。希望我们可以稍后填写详细信息!当编译器想要显示缺少的类型信息时,它会打印下划线(例如 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
outlives'a
,表示为'j: 'a
.
- 我们有
next: &'a ...
而 next
是 vincent
,所以(就像上面一样),'a
不能比 vincent
长寿。 'v
比 'a
=> 'v: 'a`.
- 最后,
Policeman<'a>
中的 '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
,我们在程序中对所有生命周期施加了很多限制。这很快就会导致限制中的矛盾,就像这里发生的那样。
我的小例子的快速修复
...就是去掉self
参数中的'a
:
fn set_next(&mut self, next: &'a Policeman<'a>)
这样我们就删除了不必要的限制。不幸的是,这还不足以编译整个示例。
更通用的解决方案
我不是很熟悉你提到的设计模式,但从它的外观来看,在编译时跟踪所涉及的生命周期几乎是不可能的。因此我会使用 Rc
或 Arc
而不是引用。使用这些智能指针,您不需要注释生命周期和所有内容 "just works"。唯一的缺点:一点点运行时成本。
但不可能告诉您最佳解决方案:这实际上取决于手头的问题。
解释了为什么这不起作用,您应该考虑使用智能指针——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) because the struct Officer
is covariant with respect to 'a
. That means that if you have an Officer<'a>
, you can treat it like an Officer<'b>
as long as 'a: 'b
; that is, when 'a
outlives 'b
, Officer<'a>
is a subtype of Officer<'b>
. This knowledge lets the compiler shrink the lifetimes of each reference in the way you probably expected at first. (There's another 关于您可能喜欢的变化,尽管它并不完全适用于您的情况。)
,所以 Policeman<'a>
不是 Policeman<'b>
的子类型。这剥夺了编译器调整生命周期的能力:引用 &'_ john
可能有更短的生命周期,但 Policeman<'_>
特征不能。这就是为什么即使是 Lukas 的 "quick fix" 也不适用于您的整个示例。
至少还有一种方法可以通过添加生命周期参数使原始示例工作,这样 set_next
就不会统一 &'?first Policeman<'?second>
中的两个生命周期,但从该更改中您只能得到一个额外的间接层——也就是说,它会使示例工作,但如果你添加 michael
谁报告给 martin
,你会回到你开始的地方。
我正在尝试在 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
好的,现在我们准备好一步一步地看看编译器在想什么:
{ // start 'v let vincent = Officer::new(8); // : Officer<'?arg_vincent>
Rust 还不知道生命周期参数,因此它在这里只能推断出一个不完整的类型。希望我们可以稍后填写详细信息!当编译器想要显示缺少的类型信息时,它会打印下划线(例如 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
outlives'a
,表示为'j: 'a
. - 我们有
next: &'a ...
而next
是vincent
,所以(就像上面一样),'a
不能比vincent
长寿。'v
比'a
=> 'v: 'a`. - 最后,
Policeman<'a>
中的'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
,我们在程序中对所有生命周期施加了很多限制。这很快就会导致限制中的矛盾,就像这里发生的那样。
我的小例子的快速修复
...就是去掉self
参数中的'a
:
fn set_next(&mut self, next: &'a Policeman<'a>)
这样我们就删除了不必要的限制。不幸的是,这还不足以编译整个示例。
更通用的解决方案
我不是很熟悉你提到的设计模式,但从它的外观来看,在编译时跟踪所涉及的生命周期几乎是不可能的。因此我会使用 Rc
或 Arc
而不是引用。使用这些智能指针,您不需要注释生命周期和所有内容 "just works"。唯一的缺点:一点点运行时成本。
但不可能告诉您最佳解决方案:这实际上取决于手头的问题。
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) because the struct Officer
is covariant with respect to 'a
. That means that if you have an Officer<'a>
, you can treat it like an Officer<'b>
as long as 'a: 'b
; that is, when 'a
outlives 'b
, Officer<'a>
is a subtype of Officer<'b>
. This knowledge lets the compiler shrink the lifetimes of each reference in the way you probably expected at first. (There's another
Policeman<'a>
不是 Policeman<'b>
的子类型。这剥夺了编译器调整生命周期的能力:引用 &'_ john
可能有更短的生命周期,但 Policeman<'_>
特征不能。这就是为什么即使是 Lukas 的 "quick fix" 也不适用于您的整个示例。
至少还有一种方法可以通过添加生命周期参数使原始示例工作,这样 set_next
就不会统一 &'?first Policeman<'?second>
中的两个生命周期,但从该更改中您只能得到一个额外的间接层——也就是说,它会使示例工作,但如果你添加 michael
谁报告给 martin
,你会回到你开始的地方。