3

I am trying to lazy-initialize a simple tree on first access of a node.

My tree struct:

struct Tree {
    true_branch: Option<Box<Tree>>,
    false_branch: Option<Box<Tree>>,
}

To do the lazy initialization on first access I would like to return a Result<&mut Tree, &mut Tree> which is an Ok(requested_node) if the node is already initialized or an Err(last_initialized_on_the_path_to_requested_node) so that the lazy initialization can continue from that node.

Getting a node, if it initialized works fine with get_mut but I cannot get the Result version, get_last_initialized_mut, to compile.

impl Tree {
    fn get_mut(&mut self, mut directions: impl Iterator<Item = bool>) -> Option<&mut Self> {
        let mut current = self;
        loop {
            match directions.next() {
                None => break,
                Some(true) => {
                    current = current.true_branch.as_deref_mut()?;
                }
                Some(false) => {
                    current = current.false_branch.as_deref_mut()?;
                }
            }
        }
        Some(current)
    }
    
    /// This does not compile
    fn get_last_initialized_mut(&mut self, mut directions: impl Iterator<Item = bool>) -> Result<&mut Self, &mut Self> {
        let mut current = self;
        loop {
            match directions.next() {
                None => break,
                Some(true) => {
                    let next = current.true_branch.as_deref_mut();
                    if next.is_none() {
                        drop(next);
                        return Err(current);
                    } else {
                        current = next.unwrap();
                    }
                }
                Some(false) => {
                    let next = current.false_branch.as_deref_mut();
                    if next.is_none() {
                        drop(next);
                        return Err(current);
                    } else {
                        current = next.unwrap();
                    }
                }
            }
        }
        Ok(current)
    }
}

The error:

error[E0499]: cannot borrow `*current` as mutable more than once at a time
  --> src/lib.rs:36:36
   |
27 |     fn get_last_initialized_mut(&mut self, mut directions: impl Iterator<Item = bool>) -> Result<&mut Self, &mut Self> {
   |                                 - let's call the lifetime of this reference `'1`
...
33 |                     let next = current.true_branch.as_deref_mut();
   |                                ---------------------------------- first mutable borrow occurs here
...
36 |                         return Err(current);
   |                                    ^^^^^^^ second mutable borrow occurs here
...
52 |         Ok(current)
   |         ----------- returning this value requires that `current.true_branch` is borrowed for `'1`

What I do not get is that I drop next on line 35 and then it says current is still borrowed on line 36, by the borrow by next on line 33.

I have tried breaking with an enum and returning outside of the loop based on the break type, dropping variables. Neither of them worked.

Playground

susitsm
  • 465
  • 2
  • 6
  • This is sadly a known [limitation of the NLL rules](https://docs.rs/polonius-the-crab/latest/polonius_the_crab/#rationale-limitations-of-the-nll-borrow-checker) and there isn't a great canonical workaround for it. I've not done an in-depth analysis, but if you've checked this for soundness, and can't come up with a different API, you can resort to `unsafe` to promise the compiler this is okay. – isaactfa Aug 18 '22 at 13:51
  • I tried everything I could come up with and yeas, the borrow checker expand some lifetime after some return and it broke what you want to do, so you will need some unsafe: [playgound](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bef3e248f9bb309547114af62b16cdca) – Bamontan Aug 18 '22 at 18:30
  • Mutually recursive data types in rust are hard, here's a few pointers and ways to implement what you're trying to achieve without `unsafe`: https://stackoverflow.com/a/45109686/490790 – SeedyROM Aug 19 '22 at 01:07
  • Thanks! The problem is as @isaactfa said an NLL limitation, [the first one in the rule list](https://github.com/rust-lang/rust/issues/54663). Currently I use the workaround of returning a `Result<&mut Tree, TreeIndex>` then looking up the `Err(index)` variant with `get_mut`, which is not ideal but works. If it ever becomes a performance bottleneck `unsafe` is the way to go. – susitsm Aug 19 '22 at 10:10

0 Answers0