Mutable borrow in loop / borrow checker query

I have a gnarly borrow checker issue which I'd appreciate some help with from some veteran rustaceans. A very reduced example of my struggles is below.

The real production code is performance critical, so I don't want to be adding extra runtime overhead, but I would be willing to use unsafe, if someone could recommend the correct cure! :slight_smile: -- or possibly use polonius-the-crab trait as per Workaround before Polonius makes it to stable if it fixes things.

But first I wanted to triple check - is my attempted usage sound? In particular, is it reasonable to believe that traverse should pass borrow checking? (traverse_once does pass borrow checking, so in concept I don't see why traverse wouldn't be sound too - it just modifies N times before returning a value, instead of once).

I assume because I have a unique reference to self, I should be able to call Step any number of times before returning a reference to mutable state under self? Does this seem right?

If so, what solutions are there in stable rust before polonius is stable? Is polonius-the-crab the best? Alternatively, is there a nice way of doing this with unsafe without just casting to a raw pointer and back?

Error

    |
248 |     fn traverse<'t>(&'t mut self) -> Output<'t> {
    |                 -- lifetime `'t` defined here
249 |         loop {
250 |             match Self::step(&self.bytes, &mut self.offset) {
    |                                           ^^^^^^^^^^^^^^^^ `self.offset` was mutably borrowed here in the previous iteration of the loop
251 |                 ControlFlow::Continue(()) => {},
252 |                 ControlFlow::Break(output) => return output,
    |                                                      ------ returning this value requires that `self.offset` is borrowed for `'t`

Code

use core::ops::ControlFlow;
type Output<'t> = &'t mut usize;

struct ExampleTraverser {
    bytes: Vec<u8>,
    offset: usize,
}

impl ExampleTraverser {
    fn traverse_once<'t>(&'t mut self) -> ControlFlow<Output<'t>> {
        match Self::step(&self.bytes, &mut self.offset) {
            ControlFlow::Continue(()) => ControlFlow::Continue(()),
            ControlFlow::Break(output) => return ControlFlow::Break(output)
        }
    }

    fn traverse<'t>(&'t mut self) -> Output<'t> {
        loop {
            match Self::step(&self.bytes, &mut self.offset) {
                ControlFlow::Continue(()) => {},
                ControlFlow::Break(output) => return output,
            };
        }
    }

    fn step<'s>(bytes: &[u8], offset: &'s mut usize) -> ControlFlow<Output<'s>> {
        if *offset >= bytes.len() {
            ControlFlow::Break(offset)
        } else {
            *offset = *offset + 1;
            ControlFlow::Continue(())
        }
    }
}
1 Like

Could you please add the (full) compiler error to your post? This saves the reader some time.

1 Like

Yes, or at least your sample code looks like a classic "problem case #3" to me.

The crate lists some possibilities, if applicable to your actual use case. Hard to answer from your sample code since it's so reduced, but the general idea is to delay creation of the borrow until in the returning branch, or recreate the borrow in the returning branch.

        loop {
            match Self::step(&self.bytes, &mut self.offset) {
                ControlFlow::Continue(()) => {},
-               ControlFlow::Break(output) => return output,
+               ControlFlow::Break(_) => return &mut self.offset,
            };
        }

I recommend polonius_the_crab over your own unsafe, if that works for you.

4 Likes

Sorry, :man_facepalming:, obvious omission. I've updated my post! :slight_smile:

1 Like

Many thanks for your help / validation!

Sadly delaying the borrow isn't possible, because via a visitor pattern this Output<'t> is actually a V::Output<'t> associated type that can be constructed in user code in a GAT.

I'll see if I can get the polonius the crab trait working -- if not I'll fall back to some carefully commented unsafe:

    fn traverse_unsafe<'t>(&'t mut self) -> Output<'t> {
        loop {
            // SAFETY: Work around the current borrow checker as per this thread
            // https://users.rust-lang.org/t/mutable-borrow-in-loop-borrow-checker-query/118081/3
            // Remove once polonius is out
            let offset = unsafe { &mut *(&mut self.offset as *mut _) };
            match Self::step(&self.bytes, offset) {
                ControlFlow::Continue(()) => {},
                ControlFlow::Break(output) => return output,
            };
        }
    }

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.