Lifetime issues with mutable borrow in a loop

I'm working on refactoring code from owned data to borrowed data, and I've hit a lifetime issue with mutable borrows in a loop. The working version (with owned data) compiles fine, but the borrowed version fails.

The Problem:
I have a trait method that takes a mutable scratch space and returns an iterator. When the iterator borrows from the scratch space, I can't call this method multiple times in a loop:

fn ref_records<'point, 'scratch, ScratchT: ScratchSpaceRegistry>(
    &'point self,
    scratch: &'scratch mut ScratchT,
) -> RefIterResult<'point>  // Returns Result<Box<dyn Iterator<Item = RefRecord<'point>> + Send + 'point>, String>
where
    'scratch: 'point;

When I try to call this in a loop:

for point in data {
    let records = point.ref_records(scratch)?;
    Self::handle_point_ref(point, records, &mut inserter).await?;
}

Compiler Error:

cannot borrow `*scratch` as mutable more than once at a time
`*scratch` was mutably borrowed here in the previous iteration of the loop

The Working Version (for comparison):
This version works fine because the iterator doesn't borrow from scratch:

fn records<'point, 'scratch, ScratchT: ScratchSpaceRegistry>(
    &'point mut self,
    scratch: &'scratch mut ScratchT,
) -> OwnedIterResult  // Returns Result<Box<dyn Iterator<Item = Record> + Send>, String>
where
    'scratch: 'point;

The returned iterator contains owned Record values with no lifetime parameters, so the borrow of scratch ends immediately.

My Question:

Is there a way to express that the mutable borrow of scratch should only last as long as needed for deserialization, even though the returned iterator lives for 'point? Or is this a fundamental limitation where I need to restructure my API? If I need API restructuring, what would be a good way to handle this ?

The full code is available here: Rust Playground

I've tried various lifetime annotations but can't find a way to tell the compiler that scratch is only borrowed temporarily during iterator construction, not for the iterator's entire lifetime.

Not your API, just the loop. for loop can only be used with normal iterator, while it seems like you want a lending iterator.

Lending iterators were the motivational example for GATs, yet even now, years after stabilization of GATs they are not part of for interface and only exist as third-party implementations.

Maybe in Rust 2030 or Rust 2060 you would be able to write what you want to write, but today you need third-party crate and while loop, sorry.

I found the crate for lending iterators at crates.io: Rust Package Registry but I am not sure how to fully use that to solve my problem. I tried something like this

        let mut iter = data.iter().into_lending_iter();
        while let Some(point) = iter.next() {
            let records = point.ref_records(scratch)?;
            Self::handle_point_ref(point, records, &mut inserter).await?;
        }

but that did not work, I still get the same error. Isn't my issue with lifetimes of the mutable borrow ? I was not able to connect how it related to lending iterators with GATs

Why do you use scratch? Do you keep something there while iterating or not? Where do these items that you are iterating actually live?

If the data that your iterator is producing lives in the iterator itself and you couldn't have all the items returned from the iterator to exist simultaneously the it's what lending iterator is supposed to resolve.

But I'm now unsure if that's how you are using scratch… this may be the problem that use<…> tries to resolve, maybe?

I use scratch so that serialized bytes can be decoded to that space. Reusing the scratch avoid allocations for the structs as this is done on the hot path. In my real use case, the Record struct is much larger and has multiple fields inside it

While I am iterating, data lives inside scratch. In the owned case (which compiles, i decode to scratch and then move data to the iterator). For the borrowed case, I want data to live in scratch

Then how can many decoded items exist, at the same time?

Interface of normal iterator guarantees that this:

   let x = iter.next();
   let y = iter.next();

Is always valid and all items should be able to exist at the same time.

Lending iterator may have a buffer inside that it would be able to “lend”… but, of course said buffer should be tied to the iterator, somehow.

All the decoded data lives in there, simultaneously? Or do you want to keep it there, temporarily?

But then you need to resolve lifetime issues… before you can tell about them to compiler, you need to understand them, yourself, though.

Currently you say that your iterator only produces elements that are tied to the 'point and never uses 'scratch at all. If your iterator keeps all the elemets in 'scratch, simultaneously, then your RefIterResult<'scratch> should reflect that. If your iterator doesn't do that and couldn't make two elements that would coexist, then that's the case for the lending iterator.

You also might be able to leverage Rc::make_mut to make a pseudo-lending iterator which will reuse the scratch space in the common case that elements are dropped in each iteration of the loop, but automatically makes new allocations as necessary if the consumer keeps them around for some reason.

At a given time, only one decoded item can exist in the scratch space.

The decoded data lives in the scratch space until overwritten. It is not cleared because it is a waste of cpu to clear that.

The lifetime is tied to 'point and not to 'scratch to also allow the trait to be implemented for the already decoded object. The impl of the trait for Vec<u8> uses the scratch space whereas the impl of the trait for DecodedData directly does not use the scratch.

So, in some sense, yes the returning iterator has a reference to the data in scratch. So only one decoded object at a time, so maybe that's a case for lending iterator ?

Thanks for all the help, do you mind giving a small example or a pointer to how I could use the lending iterator to solve this scratch space problem ?

Well… that's the definition of the lending iterator, then: one can only get one item from it and have to drop the previous item to get the new one.

Sure, but it wouldn't work with undecoded object. That's the origin of the whole story with GATs and lending iterators, that spans a decade: you can always treat lending iterator as a normal iterator, but you couldn't do the opposite.

That means that your “already decided object” would have to implement two separate traits: a normal iterator that may return as many objects as you want and also a lending iterator that can only return one object at time.

While “decode on the go” function would only implement one trait.

Lifetimes, in Rust, don't work like that “do this for one type, but that for another one”, sorry. You couldn't say “trust me, bro, I know what I'm doing” and still say safe. “Trust me, bro, I know what I'm doing” is the very definition of unsafe, but well… unsafe is unsafe, it's dangerous (even if, sometimes, pretty useful).

You would need to describe these rules to the Rust compiler, somehow.

That's what lifetime markup is: it describes your lifetime story to the compiler and compiler verifies that it's correct and safe… that the whole point, they don't have any other usage.

That's precisely the idea behind landing iterator, yes.

The lending_iterator have an example.

And the blog post that I referenced explains the story, too: in a lending iterator the item that's returned from next borrows from &mut Self, from unique reference — and that makes it possible to work with it in the fashion that you want.

Of course for that to work your RefIterResult<'point> should be implementing the lending iterator interface, and not iterator interface.

Version that's implemented for decoded object may, of course, be a normal iterator, but this would simply never work for you want: normal iterator have an interface than promises, to the compiler, that you may get and hold references to as many elements as you want. As many as you want, without limitation.

Minor typo, it's the other direction.

Incidentally, this week I've been working on a lending iterator crate. I prefer lender over lending-iterator, other than all the unsoundness in lender (which is mostly caused by accidentally assuming that lent items' lifetimes are covariant without actually enforcing that).

My end goal is to provide an Iterator equivalent, an iter-tools equivalent, and utilities for cursor-like lending iterators over collections (with prev, current, next, etc instead of just next).