How can I assist the borrow checker

I believe this example should compile. How can I get the compiler to accept this bit of code?

struct Cat {
    v: Vec<usize>,
}

impl Cat {
    fn meow(&mut self) -> Option<&mut usize> {
        let at_zero = self.v.get_mut(0);
        if at_zero.is_some() {
            return at_zero;
        }

        return self.v.get_mut(1);
    }
}

My reasoning is that if we hit the first return, then the second borrow never takes place. And if we get to the second return, the first borrow should have expired by now?

error[E0499]: cannot borrow `self.v` as mutable more than once at a time
  --> src/lib.rs:12:16
   |
6  |     fn meow(&mut self) -> Option<&mut usize> {
   |             - let's call the lifetime of this reference `'1`
7  |         let at_zero = self.v.get_mut(0);
   |                       ------ first mutable borrow occurs here
8  |         if at_zero.is_some() {
9  |             return at_zero;
   |                    ------- returning this value requires that `self.v` is borrowed for `'1`
...
12 |         return self.v.get_mut(1);
   |                ^^^^^^ second mutable borrow occurs here

This code is based on a larger problem I'm trying to solve. The actual code is something along the lines of...

struct Generator {
    v: Vec<Option<usize>>,
    i: usize,
}

impl Generator {
    fn new() -> Self {
        Generator {
            v: vec![
                Some(0), None, Some(1), Some(2), None, None, None, Some(3), None
            ],
            i: 0,
        }
    }

    fn depleted(&self) -> bool {
        self.v.len() == self.i
    }

    fn next(&mut self) -> Option<&usize> {
        let next = self.v.get(self.i);
        self.i += 1;

        match next {
            Some(next) => next.as_ref(),
            None => None,
        }
    }
}

struct Transformer {
    generator: Generator,
}

impl Transformer {
    fn new() -> Self {
        Transformer {
            generator: Generator::new(),
        }
    }

    fn next(&mut self) -> Option<&usize> {
        while !self.generator.depleted() {
            let next = self.generator.next();
            if next.is_some() {
                return next;
            }
        }

        return None;
    }
}

Something like this might be what you're looking for? You only need an immutable reference to make the comparison. The mutable reference you initially took does not expire by the time you reach the second return - you can verify this by trying to return it, which you'd find would work.

struct Cat {
    v: Vec<usize>,
}

impl Cat {
    fn meow(&mut self) -> Option<&mut usize> {
        let at_zero = self.v.get(0);
        if at_zero.is_some() {
            return self.v.get_mut(0);
        } else {
            return self.v.get_mut(1);
        }
    }
}

I find it helpful to think about references in terms of scope. The reference is valid so long as it remains in scope - you can't have two mutable references in the same scope, because they'd exist at the same time! Instead, you can just use an immutable reference to peek at the value you want to compare against, then take the appropriate mutable reference in a new scope. If you're dealing with something where Time-of-check to time-of-use is a concern, you might need a different approach.

In my actual problem, I have an object which generates values via fn next(&mut self) -> Option<&Self::Output> and I want to wrap this object in a transformer which skips None next values by naively polling the generator in a loop. The size of Self::Output is large so I would prefer to return some sort of reference.

In the shorter example, I've used get_mut() as an example since it relies on &mut self similar to next(). I'm confused why this works:

impl Cat {
    fn meow(&mut self) -> Option<&mut usize> {
        let a = self.v.get_mut(0);
        return a;
        return self.v.get_mut(0);
    }
}

But this doesn't?

impl Cat {
    fn meow(&mut self) -> Option<&mut usize> {
        let a = self.v.get_mut(0);
        if true {
            return a;
        }

        // error: cannot borrow `self.v` as mutable more than once at a time
        return self.v.get_mut(0);
    }
}

Is there no way to help the borrow checker figure out that adding the if statement doesn't change anything?

It's a known shortcoming of the current borrow checker. AKA "conditional return of a borrow" or "NLL problem case #3".

You could put the Transformer::next logic into a Generator method.

    fn next_flat(&mut self) -> Option<&usize> {
        let res = self.v[self.i..]
            .iter()
            .enumerate()
            .find(|&(_, opt)| opt.is_some());

        match res {
            None => {
                self.i = self.v.len();
                None
            }
            Some((offset, r)) => {
                self.i += offset + 1;
                r.as_ref()
            }
        }
    }
4 Likes

The second return is dead code; there's no conditional return.

The analysis doesn't consider values, so despite the literal true, there's not considered to be dead code[1] and there is considered to be a conditional return.


  1. try replacing return a with drop(a); return None to demonstrate a lack of dead code warnings â†Šī¸Ž

1 Like