如何使用特征对象链实现责任链模式? [英] How do I implement the Chain of Responsibility pattern using a chain of trait objects?
问题描述
我正在尝试在Rust中实施责任链设计模式:
pub trait Policeman<’a> {
fn set_next(&’mut a self,next:&’a Policeman<’a>);
}
酒吧结构官<’a> {
扣减项:u8,
下一步:Option&’a警察'a>>
}
暗示*军官{
pub fn new(deduction:u8)->军官{
官员{扣,下一个:无}
}
}
impl<’a>警察<’a>对于官员 {
fn set_next(&’a mut self,next:&’a Policeman<’a>){
self.next = Some(next);
}
}
fn main(){
let vincent = Officer :: new(8); //-++ Vincent进入范围
let mut john = Officer :: new(5); //-+ john进入范围
let mut martin = Officer :: new(3); //-+马丁进入范围
// |
john.set_next(& vincent); // |
martin.set_next(& john); // |
} //马丁,约翰,文森特超出范围
这会产生错误消息:
错误[E0597]:`john`寿命不足
- > src\main.rs:29:1
|
27 | john.set_next(& vincent);
| ----这里借钱
28 | martin.set_next(& john);
29 | }
| ^`john`仍在这里借钱
|
=注意:作用域中的值以与创建它们相反的顺序被删除
错误[E0597]:`martin`寿命不足
-> src\main.rs:29:1
|
28 | martin.set_next(& john);
| ------这里借钱
29 | }
| ^`马丁`下降了这里,但仍然借了
|
=注意:作用域中的值以与创建它们相反的顺序被删除
错误[E0597]:`john`寿命不足
-> src\main.rs:29:1
|
28 | martin.set_next(& john);
| ----这里借钱
29 | }
| ^`john`仍在这里借钱
|
=注意:作用域中的值以创建时的相反顺序删除
为什么 john
寿命不够长吗?
- 已创建
文森特
- 已创建
john
- 已创建
martin
-
john
是指vincent
(范围是vincent
) -
martin
是指john(作用域为john
) -
martin
超出范围(john
仍在范围内) -
john
超出范围(vincent
仍在范围内) -
vincent
超出范围
我该如何更改生存期或代码正确地在Rust中实施责任链模式?
详细说明
您的问题非常有趣,并且很难直接理解为什么它不起作用。如果您了解编译器是如何进行统一的,那么它会很有帮助。我们将遍历编译器执行的所有步骤以找出类型。
为了使操作更简单,我们使用以下简化示例:
让vincent = Officer :: new(8);
let mut john = Officer :: new(5);
john.set_next(& vincent);
这将导致相同的错误消息:
< pre $ = lang-none prettyprint-override>
error [E0597]:`john`寿命不足
-> src / main.rs:26:1
|
25 | john.set_next(& vincent);
| ----这里借钱
26 | }
| ^`john`仍在这里借钱
|
=注意:作用域中的值按创建时的相反顺序删除
First ,让我们以更明确,更终身的方式转换代码:
{//开始'v
let vincent = Officer :: new(8);
{//开始’j
let mut john = Officer :: new(5);
john.set_next(& vincent);
} //结束'j
} //结束'v
好,现在我们准备逐步了解编译器的想法:
{//开始'v
let vincent = Officer :: new(8); //:官员<’?arg_vincent>
Rust还不知道寿命参数,因此可以仅在此处推导不完整的类型。希望我们以后可以填写详细信息!当编译器要显示缺少的类型信息时,它会打印一个下划线(例如 Vec< _>
)。在此示例中,我将丢失的信息写为'?arg_vincent
。这样,我们以后可以参考它。
{//开始'j
let mut john =官员:: new(5); //:官员<’?arg_john>
与上述相同。
john.set_next(& vincent);
现在变得很有趣了!编译器具有以下功能签名:
fn set_next(&'mut a 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
(因为这就是我们作为参数传递的内容)。但是’?arg_vincent
尚未修复,并且完全不受限制。因此,这不会对’a
施加限制(与前两点不同)。相反,我们对'a
的选择决定了'?arg_vincent
以后:'?arg_vincent:= 'a
。
简而言之:
'j:'a和
'v:'a
那么,像约翰和一样与文森特一样多的生命寿命是多少? ’v
是不够的,因为它比 john
寿命长。 ’j
可以;它满足上述条件。
那么一切都很好吗?没有!我们现在选择生命周期’a =’j
。因此我们也知道'?arg_vincent =‘j
!因此,文森特
的完整类型为 Officer<’j>
。这反过来告诉编译器, vincent
借用了寿命为 j
的内容。但是 vincent
的寿命比’j
的寿命长,所以它比借来的还长!那很糟。这就是编译器抱怨的原因。
这整个过程确实相当复杂,我想在阅读我的解释后,大多数人在阅读了大多数数学证明后的感觉与我完全一样:每一步都是有意义的,但是结果并不直观。 也许这可以稍微改善这种情况:
由于 set_next()
函数需要所有 be 'a
的生命周期,我们对程序中的所有生命周期都施加了很多限制。
我的小例子的快速解决方法
...是从 self
参数中删除'a code>:
fn set_next(& mut self,next:&'a Policeman<'a>)
这样做可以消除不必要的限制。不幸的是,这还不足以编译整个示例。
更通用的解决方案
我对您提到的设计模式不是很熟悉,但是从外观上看,在编译时跟踪所涉及的生命周期几乎是不可能的。因此,我将使用 Rc
或 Arc
代替引用。有了这些智能指针,您就无需注释生命周期,一切都可行。唯一的缺点是:运行时间成本很小。
但是不可能告诉您最佳的解决方案:这实际上取决于当前的问题。
I'm trying to implement the Chain of Responsibility design pattern in 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
This produces the error message:
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
Why does john
not live long enough?
- Created
vincent
- Created
john
- Created
martin
john
refers tovincent
(vincent
in scope)martin
refers tojohn (john
in scope)martin
out of scope (john
still in scope)john
out of scope (vincent
still in scope)vincent
out of scope
How do I need to change the lifetimes or the code to correctly implement the Chain of Responsibility pattern in Rust?
Detailed explanation
Your problem is quite interesting and it's certainly hard to understand directly why it doesn't work. It helps a lot if you understand how the compiler does unification. We will walk through all steps the compiler does in order to find out types.
In order to make it a bit easier, we use this simplified example:
let vincent = Officer::new(8);
let mut john = Officer::new(5);
john.set_next(&vincent);
This results in the same error message:
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
First, let's transform the code in a form that's more explicit, lifetime wise:
{ // start 'v
let vincent = Officer::new(8);
{ // start 'j
let mut john = Officer::new(5);
john.set_next(&vincent);
} // end 'j
} // end 'v
Ok, now we're prepared to see what the compiler is thinking, step by step:
{ // start 'v let vincent = Officer::new(8); // : Officer<'?arg_vincent>
Rust doesn't know the lifetime parameter yet, thus it can only deduce an incomplete type here. Hopefully we can fill out the details later! When the compiler wants to show missing type information, it prints an underscore (e.g. Vec<_>
). In this example I've written the missing information as '?arg_vincent
. That way we can refer to it later.
{ // start 'j let mut john = Officer::new(5); // : Officer<'?arg_john>
The same as above.
john.set_next(&vincent);
Now it get's interesting! The compiler has this function signature:
fn set_next(&'a mut self, next: &'a Policeman<'a>)
Now, the compiler's job is to find a fitting lifetime 'a
that satisfies a bunch of conditions:
- We have
&'a mut self
andjohn
isself
here. So'a
can't live longer thanjohn
. In other words:'j
outlives'a
, denoted'j: 'a
. - We have
next: &'a ...
andnext
isvincent
, so (just like above),'a
can't live longer thanvincent
.'v
outlives'a
=> 'v: 'a`. - Lastly, the
'a
inPoliceman<'a>
refers to the (yet to be determined) lifetime parameter'?arg_vincent
(since that's what we pass as argument). But'?arg_vincent
is not yet fixed and totally unbounded. So this doesn't impose a restriction on'a
(unlike the previous two points). Instead, our choice for'a
determines'?arg_vincent
later:'?arg_vincent := 'a
.
So in short:
'j: 'a and
'v: 'a
So what's a lifetime which lives at most as long as john and as most as long as vincent? 'v
isn't sufficient, since it outlives john
. 'j
is fine; it satisfied the conditions above.
So everything is fine? No! We chose the lifetime 'a = 'j
now. Thus we also know that '?arg_vincent = 'j
! So the full type of vincent
is Officer<'j>
. This in turn tells the compiler that vincent
borrowed something with the lifetime j
. But vincent
lives longer than 'j
, so it outlives its borrow! That's bad. That's why the compiler complains.
This whole thing is really rather complex, and I guess that after reading my explanation, most people feel exactly like I feel after reading most math proofs: each step made sense, but the result isn't intuitive. Maybe this improves the situation slightly:
Since the set_next()
function requires all lifetimes to be 'a
, we impose a lot of restrictions on all lifetimes in our program. This quickly leads to contradictions in restrictions, as it happened here.
A quick fix for my small example
... is to remove the 'a
from the self
parameter:
fn set_next(&mut self, next: &'a Policeman<'a>)
By doing that we remove unnecessary restrictions. Unfortunately, this isn't enough to make your whole example compile.
A more general solution
I'm not very familiar with the design pattern you mentions, but from the looks of it, it's close to impossible to track the involved lifetimes at compile time. Thus I'd use Rc
or Arc
instead of references. With those smartpointers, you don't need to annotate lifetimes and everything "just works". Only disadvantage: a tiny bit of runtime cost.
But it's impossible to tell you the best solution: it really depends on the problem at hand.
这篇关于如何使用特征对象链实现责任链模式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!