How to implement the chain of responsibility template using a chain of feature objects?

I am trying to implement a Chain of Responsibility design template 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 

An error message will appear:

 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 doesn't john live long enough?

  • Created by vincent
  • Created by john
  • Created by martin
  • john refers to vincent ( vincent in the area)
  • martin refers to john (john in the area)
  • martin is out of scope ( john is still in scope)
  • john is out of scope ( vincent is still in scope)
  • vincent out of sight

How can I change the lifetime or code to correctly implement the responsibility chain template in Rust?

+5
source share
2 answers

Detailed explanation

Your problem is quite interesting, and it is certainly difficult to understand why it does not work. This helps if you understand how the compiler does unification. We will go through all the steps that the compiler takes to find out the types.

To make this a little easier, we use this simplified example:

 let vincent = Officer::new(8); let mut john = Officer::new(5); john.set_next(&vincent); 

As a result, the same error message appears:

 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, we transform the code into a more explicit, time-wise form:

 { // 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 are ready to understand what the compiler thinks, step by step:

 { // start 'v let vincent = Officer::new(8); // : Officer<'?arg_vincent> 

Rust does not yet know the lifetime parameter, so an incomplete type can be displayed here. Hope we can fill out the details later! When the compiler wants to show missing type information, it prints an underscore (for example, Vec<_> ). In this example, I wrote the missing information as '?arg_vincent . So we can refer to it later.

  { // start 'j let mut john = Officer::new(5); // : Officer<'?arg_john> 

Same as above.

  john.set_next(&vincent); 

Now it is getting interesting! The compiler has this function:

 fn set_next(&'a mut self, next: &'a Policeman<'a>) 

Now the compiler’s job should find a suitable lifetime, satisfying a bunch of conditions:

  • Here &'a mut self and john here self . Therefore, 'a cannot live longer than john . In other words: 'j survives 'a , denoted by 'j: 'a .
  • We have next: &'a ... and next have vincent , so (as above) 'a cannot live longer than vincent . 'v survives 'a => 'v:' a`.
  • Finally, 'a in Policeman<'a> refers to the (not yet defined) lifetime parameter '?arg_vincent (since we pass as an argument). But '?arg_vincent has not yet been fixed and is completely unlimited. Thus, this does not impose restrictions on 'a (unlike the previous two points). Instead, our choice for 'a defines '?arg_vincent later: '?arg_vincent := 'a .

Shortly speaking:

 'j: 'a and 'v: 'a 

So, what is life that lives at best as long as john and no more than vincent? 'v not enough, since it is experiencing john . 'j excellent; It satisfies the above conditions.

So, is everything all right? No! Now we have chosen the lifetime 'a = 'j . So we also know that '?arg_vincent = 'j ! So the full vincent type is Officer<'j> . This, in turn, tells the compiler that vincent borrowed something with a lifetime j . But vincent lives longer than 'j , so he survives his loan! This is bad. This is why the compiler complains.

All this is very complicated, and I think that after reading my explanation, most people feel the way I feel after reading most of the mathematical proofs: each step makes sense, but the result is not intuitive. Perhaps this improves the situation a bit:

Since the set_next() function requires all lifetimes to be 'a , we impose many restrictions on all lifetimes in our program. This quickly leads to contradictions in restrictions, as happened here.

Quick fix for my little example

... is to remove 'a from the self parameter:

 fn set_next(&mut self, next: &'a Policeman<'a>) 

By doing this, we remove unnecessary restrictions. Unfortunately, this is not enough for your entire example to compile.

More general solution

I am not very familiar with the design pattern that you mentioned, but due to its appearance it is almost impossible to track the involved lifetimes during compilation. That way I would use Rc or Arc instead of links. With these smart controllers, you don’t need to annotate life cycles, and everything just “works.” Only flaw: a tiny bit of runtime.

But it is impossible to tell you the best solution: it really depends on the problem.

+5
source

Lucas' excellent answer explains why this will not work, and you should consider using smart pointers - either Box for single ownership or Rc / Arc for sharing.

However, you can do something similar (though not very useful) by getting rid of the Policeman trait and making set_next Officer set_next :

 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 

This works ( playground ), because the struct Officer covariant relative to 'a . This means that if you have Officer<'a> , you can treat him as Officer<'b> as long as 'a: 'b ; that is, when 'a survives 'b , Officer<'a> is a subtype of Officer<'b> . This knowledge allows the compiler to reduce the lifetime of each link in the form that you probably expected at the beginning. (There's still really good Q&A about the difference that you might like, although it is not entirely applicable to your situation.)

On the other hand, traits are always invariant with respect to their parameters; therefore, Policeman<'a> not a subtype of Policeman<'b> . This makes it impossible for the compiler to adjust its lifetime: the &'_ john link may have a shorter lifetime, but the Policeman<'_> trait cannot. That's why even a “quick fix” by Lucas will not work for your entire example.

There is at least one more way to make the original example work by adding the lifetime parameter so that set_next does not combine the two lifetimes in &'?first Policeman<'?second> , but from this change you get only one additional layer of indirection - - that is, this example will work, but if you add michael , which tells martin , you will return to where you started.

+6
source

Source: https://habr.com/ru/post/1272616/


All Articles