17

Given the following function:

use std::io::{BufRead, stdin};

fn foo() -> usize {
    let stdin = stdin();
    let stdinlock = stdin.lock();
    stdinlock
        .lines()
        .count()
}

This fails to compile with the following error:

error: `stdin` does not live long enough
  --> src/main.rs:12:1
   |
7  |     let stdinlock = stdin.lock();
   |                     ----- borrow occurs here
...
11 | }
   | ^ `stdin` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

I find this surprising because the outcome of consuming the lock (via lines) does not retain any references to the original source. In fact, assigning the same outcome to a binding before returning works just fine (Playground).

fn bar() -> usize {
    let stdin = stdin();
    let stdinlock = stdin.lock();
    let r = stdinlock
        .lines()
        .count();
    r
}

This suggests that returning a "consumed lock" immediately has led to the lock attempting to live longer than the locked content, much in an unusual way. All references that I looked into usually point out that the order of declaration matters, but not how the returned objects can affect the order in which they are released.

So why is the former function rejected by the compiler? Why is the lock being seemingly retained for longer than expected?

E_net4
  • 27,810
  • 13
  • 101
  • 139
  • 1
    Oh. Interesting! – Matthieu M. Apr 24 '17 at 19:44
  • @MatthieuM. The cause seems to be some magic around `MutexGuard`. I made an oversimplified reproduction, based on on the types only. See [here](https://gist.github.com/peterjoel/f6a1e1ec29da1ee73994820f742cdb7d#file-stdinlock_problem-rs). In the version that breaks, I'm using `MutexGuard` from `std`. In the version that works, I copy-pasted the same code instead of using `std`. – Peter Hall Apr 24 '17 at 21:15
  • 1
    Has anyone raised a bug report? – Veedrac Apr 25 '17 at 01:29
  • 1
    @Veedrac: Not that I know off, I was looking for an explanation as experience has proven time and time again that the borrow checker was more reliable than my intuition. Then again, in this case it looks more and more likely that something fishy is going on... – Matthieu M. Apr 25 '17 at 07:05
  • 5
    There's a few open issues around this topic. The main one is https://github.com/rust-lang/rust/issues/37407 – oli_obk Apr 25 '17 at 11:27
  • 2
    Discussion on this issue has proceeded in [#21114](https://github.com/rust-lang/rust/issues/21114). – E_net4 May 01 '17 at 18:26

2 Answers2

3

This seems to be a bug in the compiler. You can make the compiler happy by using an explicit return statement:

use std::io::{stdin, BufRead};

fn foo() -> usize {
    let stdin = stdin();
    let stdinlock = stdin.lock();
    return stdinlock
        .lines()
        .count();
}

fn main() {}

playground

As mentioned in the comments, there are multiple Rust issues related to this:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
musicmatze
  • 4,124
  • 7
  • 33
  • 48
2

I cannot answer the why of your question, but I can state that the current1 implementation of non-lexical lifetimes allows the original code to compile:

#![feature(nll)]

use std::io::{BufRead, stdin};

fn foo() -> usize {
    let stdin = stdin();
    let stdinlock = stdin.lock();
    stdinlock
        .lines()
        .count()
}

Playground

1 1.25.0-nightly (2018-01-11 73ac5d6)

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366