Refactoring method parameters to fields

Hello everybody! I just started with Rust but my supervisor at the university said it would be no problem to do my diploma thesis in Rust ... Well. The topic evolves around rewriting a Rust library so that we can turn it into a distributed program. For this I have to take apart methods in which the control flow goes to another component in between. I try to reduce the example so say I have this scenario:

struct Processor<'a'> {
    ...
}

impl<'a> Processor<'a> {

 fn process(
     &mut self, 
     other: OtherComponent, 
     items:&mut Collection<'_>) 
    -> bool {
     // ... 
     for item in items.iter_mut() {
         it is_suitable(item) {
             let x = {/* preprocess item */} ;
             let y = other.do_stuff(x);
             /* postprocess  item and y */
         }
     }
     // ...
    }
}

So I'll need to split this method into the first 'looping/ preprocessing' part and the /* postprocess item and y */ part, say fn process_before() and fn process_after(). To do so, I figured out to safe the items and the state of iteration in the Processor struct like:

struct Processor<'a> {
    currentCollection: Option<Collection<'_>>,
    currentIndex:usize,
    ..
}

(btw I already learned here, why I couldn't have references to the collection and the iterator so, this forum was already very helpful)

So actually my plan is now to take() the currentCollection, use a reference to it to execute
either part of the original process method and replace it afterwards back to the Processor struct.
The problem now seems to be, that the lifetime of the currentCollection reference comes from the Processor struct. So when I try to mutably borrow it as before, to loop it and use the items, I get the error that it needs to be borrowed for <'a>, derived from the Processor, so I can neither loop it, nor put it back to the Processor after usage. So basically the question is:
(How) Can I replace a mutably borrowed, lifetime annotated argument of a method like items, by taking, using and replacing a part of the struct the method is called on?

Generally speaking, when you rewrite code to be asynchronous — that is, not to complete its work while a particular call stack frame exists — you will have to rewrite it to remove lifetimes, replacing borrowed data with Rc/Arc or simple ownership.

Why?

  1. The patterns of usage which the borrow checker understands and can prove sound are those which are stack-frame-shaped — "this reference is not used beyond this function call, so it is okay to accept references whose lifetimes are no longer than this function call”.
  2. It's difficult to enforce the relationship “this must stay alive while it's borrowed” across independent asynchronous tasks. And, it also introduces a dependency that you don't want anyway — one must be blocked, making no progress, while the other runs.

There are exceptions to this — for example, an async fn can call another async fn and pass borrowed data — but you can't take advantage of that in a distributed program because you're going to need to ship the data off to another machine anyway.

This does not mean you need to remove all lifetimes from your program; only that data used by the algorithms you want to make “distributed” must not contain borrows / have lifetimes.

1 Like

Hi kpreid,

Thank you for taking the time. And in particular for the "Why" part because understanding this is actually more important than the implementation. I'm not actually rewriting it to async. We're working on a compiler, that transformes code to a functional representation, derives independent tasks and wraps them to threads/processes/or whatever, depending on the backend architecture.

But if I understand your point correctly it means, that I need to refactor Collection<'_> itself, so that it owns everything instead of holding references, which in turn will alleviate the need for lifetime parameters. Is this correct?

I'm not actually rewriting it to async .

By “asynchronous code” I don't mean async code, but anything that, as I said, doesn't complete its work while a particular call stack frame exists.

But if I understand your point correctly it means, that I need to refactor Collection<'_> itself, so that it owns everything instead of holding references, which in turn will alleviate the need for lifetime parameters.

Yes, that is true.

It makes sense, now that you said it. It's really interesting how Rust enforces lots of things that otherwise the compiler (if it was in a presentable condition :roll_eyes:) would have to enforce as a programming model