0

I'm working on a piece of code that has some structs which contain references to each other, and I've run into an issue that I can't seem to figure out.

// demonstration of what the problem is

struct State<T>(T);
struct Query<'a, T>(&'a T);

impl<T> State<T> {
    fn query(&mut self) -> Option<Query<'_, T>> {
        unimplemented!()
    }
}

struct Container<T>(Vec<State<T>>);

impl<T> Container<T> {
    fn query(&mut self) -> Option<Query<'_, T>> {
        let query = self.0.last_mut()?.query();

        if let Some(query) = query {
            return Some(query);
        }

        // i feel like i should be able to use &mut self again here but i can't :(

        self.0.pop(); 
        // error[E0499]: cannot borrow `*self` as mutable more than once at a time
        return self.query();
        // error[E0499]: cannot borrow `*self` as mutable more than once at a time
    }
}

Why, in this example, can I not borrow again after the if block? Why does the lifetime of the borrow continue on after returning?

kalkronline
  • 459
  • 2
  • 12
  • The workaround in your case is difficult. I got it to compile with unsafe, but I'm not sure if this is sound https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=87ea8b6a4e2150a602bababfbb7c4332 – drewtato Jul 15 '23 at 01:03
  • Here's an iterative one so you don't accidentally blow the stack https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f3e9605af972f84d3dea0bcbe804631e – drewtato Jul 15 '23 at 01:05
  • @drewtato It is sound. – Chayim Friedman Jul 15 '23 at 19:25

2 Answers2

1

This is a known limitation of the current borrow checker. It will be fixed with the introduction of the new polonius borrow checker.

Currently, there are two ways to work around it:

  • use unsafe
  • reword the code so that the borrow checker understands it

The unsafe route can usually be abstracted by using the polonius_the_crab crate, to make sure your problem actually fits this usecase, and to keep Rusts compile time safety guarantees.

In your case, this is how this would look like using polonius_the_crab:

use polonius_the_crab::prelude::*;

struct State<T>(T);
struct Query<'a, T>(&'a T);

impl<T> State<T> {
    fn query(&mut self) -> Option<Query<'_, T>> {
        unimplemented!()
    }
}

struct Container<T>(Vec<State<T>>);

impl<T> Container<T> {
    fn query(&mut self) -> Option<Query<'_, T>> {
        let mut this = self;

        polonius!(|this| -> Option<Query<'polonius, T>> {
            match this.0.last_mut() {
                None => polonius_return!(None),
                Some(state) => {
                    if let Some(query) = state.query() {
                        polonius_return!(Some(query));
                    }
                }
            };
        });

        this.0.pop();
        return this.query();
    }
}

The reason why we need to let mut this = self; is because internally, the polonius! macro takes ownership of this, uses it, and then at the end write this back to the variable. And &mut self cannot be written into. There sadly is no way to write &mut self as mut self: &mut Self, so we need to store it in a temporal variable I chose to name this.

Essentially, where you write:

i feel like i should be able to use &mut self again here but i can't

This is exactly what this crate fixes.

A more in-depth explanation of this problem and its possible other solutions can be found in the polonius_the_crab documentation.

Finomnis
  • 18,094
  • 1
  • 20
  • 27
0

With the unsightly fact that query() is called twice, this solution is possible:

impl<T> Container<T> {
    fn query(&mut self) -> Option<Query<'_, T>> {
        if self.0.is_empty() { return None; }
        if self.0.last_mut().unwrap().query().is_some() {
            self.0.last_mut().unwrap().query()
        } else {
            self.0.pop();
            self.query()
        }
    }
}
Kaplan
  • 2,572
  • 13
  • 14