4

I would naively expect the two function try_drain_* below to have the same behavior, yet the first fails to compile while the second is executed without issue.

struct Container {
    map: RefCell<HashMap<i32, i32>>,
}

impl Container {
    fn try_drain_inline(&self) {
        self.map.borrow_mut().drain();
    }

    fn try_drain_broken_down(&self) {
        let mut guard = self.map.borrow_mut();
        guard.drain();
    }
}

The borrow checker complains about try_drain_inline on the playground:

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:15:5
   |
14 |         self.map.borrow_mut().drain();
   |         ---------------------        ^ temporary value dropped here while still borrowed
   |         |
   |         temporary value created here
   |
   = note: values in a scope are dropped in the opposite order they are created

whereas it is fine with try_drain_broken_down.

It seems that there is an issue with the order in which the temporaries it created are destroyed; and "manually" materializing the temporaries rectifies the situation...

Why would the borrow checker reject the inline form and accept the broken down one?


Note: my real code is a try_pop function which requires TWO intermediate variables:

fn try_pop(&self) -> Option<i32> {
    let mut guard = self.map.borrow_mut();
    let mut drain = guard.drain();
    drain.next().map(|(_, t)| t)
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    Possibly related: https://stackoverflow.com/questions/43590162/when-returning-the-outcome-of-consuming-a-stdinlock-why-was-the-borrow-to-stdin; https://github.com/rust-lang/rust/issues/21114 – Matthieu M. Jul 22 '17 at 18:07
  • Maybe this is related to this: http://smallcultfollowing.com/babysteps/blog/2017/07/11/non-lexical-lifetimes-draft-rfc-and-prototype-available/ – Boiethios Jul 24 '17 at 07:21

1 Answers1

2

Because the inline version is equivalent to the following code:

fn try_drain_inline_broken_down(&self) {
    {   // Implicit block around the whole expression
        let mut guard = self.map.borrow_mut();
        guard.drain()    // No semicolon here
    };                   // The semicolon is here
}

Once written like this you'll notice that the value returned by drain needs to live long enough to be returned from the block, but it keeps a reference to guard which only lives until the end of the block.

In your broken down version, the value returned by drain only lives until the semicolon whereas guard lives until the end of the block so everything is fine.

Jmb
  • 18,893
  • 2
  • 28
  • 55